Merge branch 'develop' into experimental

This commit is contained in:
Bruno Windels 2018-11-06 15:28:20 +01:00
commit c19b593f5c
7 changed files with 154 additions and 19 deletions

View file

@ -199,25 +199,12 @@ module.exports = function (config) {
'matrix-react-sdk': path.resolve('test/skinned-sdk.js'),
'sinon': 'sinon/pkg/sinon.js',
// To make webpack happy
// Related: https://github.com/request/request/issues/1529
// (there's no mock available for fs, so we fake a mock by using
// an in-memory version of fs)
"fs": "memfs",
},
modules: [
path.resolve('./test'),
"node_modules"
],
},
node: {
// Because webpack is made of fail
// https://github.com/request/request/issues/1529
// Note: 'mock' is the new 'empty'
net: 'mock',
tls: 'mock'
},
devtool: 'inline-source-map',
externals: {
// Don't try to bundle electron: leave it as a commonjs dependency

View file

@ -76,7 +76,6 @@
"lodash": "^4.13.1",
"lolex": "2.3.2",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"memfs": "^2.10.1",
"optimist": "^0.6.1",
"pako": "^1.0.5",
"prop-types": "^15.5.8",

View file

@ -62,6 +62,35 @@ function createAccountDataAction(matrixClient, accountDataEvent) {
};
}
/**
* @typedef RoomAccountDataAction
* @type {Object}
* @property {string} action 'MatrixActions.Room.accountData'.
* @property {MatrixEvent} event the MatrixEvent that triggered the dispatch.
* @property {string} event_type the type of the MatrixEvent, e.g. "m.direct".
* @property {Object} event_content the content of the MatrixEvent.
* @property {Room} room the room where the account data was changed.
*/
/**
* Create a MatrixActions.Room.accountData action that represents a MatrixClient `Room.accountData`
* matrix event.
*
* @param {MatrixClient} matrixClient the matrix client.
* @param {MatrixEvent} accountDataEvent the account data event.
* @param {Room} room the room where account data was changed
* @returns {RoomAccountDataAction} an action of type MatrixActions.Room.accountData.
*/
function createRoomAccountDataAction(matrixClient, accountDataEvent, room) {
return {
action: 'MatrixActions.Room.accountData',
event: accountDataEvent,
event_type: accountDataEvent.getType(),
event_content: accountDataEvent.getContent(),
room: room,
};
}
/**
* @typedef RoomAction
* @type {Object}
@ -201,6 +230,7 @@ export default {
start(matrixClient) {
this._addMatrixClientListener(matrixClient, 'sync', createSyncAction);
this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction);
this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction);
this._addMatrixClientListener(matrixClient, 'Room', createRoomAction);
this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction);
this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction);

View file

@ -82,6 +82,8 @@ const SIMPLE_SETTINGS = [
{ id: "TagPanel.disableTagPanel" },
{ id: "enableWidgetScreenshots" },
{ id: "RoomSubList.showEmpty" },
{ id: "pinMentionedRooms" },
{ id: "pinUnreadRooms" },
{ id: "showDeveloperTools" },
];

View file

@ -249,6 +249,8 @@
"Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)",
"Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room",
"Room Colour": "Room Colour",
"Pin unread rooms to the top of the room list": "Pin unread rooms to the top of the room list",
"Pin rooms I'm mentioned in to the top of the room list": "Pin rooms I'm mentioned in to the top of the room list",
"Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets",
"Show empty room list headings": "Show empty room list headings",
"Collecting app version information": "Collecting app version information",

View file

@ -276,6 +276,16 @@ export const SETTINGS = {
default: true,
controller: new AudioNotificationsEnabledController(),
},
"pinMentionedRooms": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Pin rooms I'm mentioned in to the top of the room list"),
default: false,
},
"pinUnreadRooms": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Pin unread rooms to the top of the room list"),
default: false,
},
"enableWidgetScreenshots": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Enable widget screenshots on supported widgets'),

View file

@ -17,6 +17,7 @@ import {Store} from 'flux/utils';
import dis from '../dispatcher';
import DMRoomMap from '../utils/DMRoomMap';
import Unread from '../Unread';
import SettingsStore from "../settings/SettingsStore";
/**
* A class for storing application state for categorising rooms in
@ -53,6 +54,24 @@ class RoomListStore extends Store {
"im.vector.fake.archived": [],
},
ready: false,
// The room cache stores a mapping of roomId to cache record.
// Each cache record is a key/value pair for various bits of
// data used to sort the room list. Currently this stores the
// following bits of informations:
// "timestamp": number, The timestamp of the last relevant
// event in the room.
// "notifications": boolean, Whether or not the user has been
// highlighted on any unread events.
// "unread": boolean, Whether or not the user has any
// unread events.
//
// All of the cached values are lazily loaded on read in the
// recents comparator. When an event is received for a particular
// room, all the cached values are invalidated - forcing the
// next read to set new values. The entries do not expire on
// their own.
roomCache: {},
};
}
@ -84,6 +103,8 @@ class RoomListStore extends Store {
!payload.isLiveUnfilteredRoomTimelineEvent ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._clearCachedRoomState(payload.event.getRoomId());
this._generateRoomLists();
}
break;
@ -111,6 +132,8 @@ class RoomListStore extends Store {
if (liveTimeline !== eventTimeline ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._clearCachedRoomState(payload.event.getRoomId());
this._generateRoomLists();
}
break;
@ -119,6 +142,13 @@ class RoomListStore extends Store {
this._generateRoomLists();
}
break;
case 'MatrixActions.Room.accountData': {
if (payload.event_type === 'm.fully_read') {
this._clearCachedRoomState(payload.room.roomId);
this._generateRoomLists();
}
}
break;
case 'MatrixActions.Room.myMembership': {
this._generateRoomLists();
}
@ -216,11 +246,18 @@ class RoomListStore extends Store {
}
});
// Note: we check the settings up here instead of in the forEach or
// in the _recentsComparator to avoid hitting the SettingsStore a few
// thousand times.
const pinUnread = SettingsStore.getValue("pinUnreadRooms");
const pinMentioned = SettingsStore.getValue("pinMentionedRooms");
Object.keys(lists).forEach((listKey) => {
let comparator;
switch (RoomListStore._listOrders[listKey]) {
case "recent":
comparator = this._recentsComparator;
comparator = (roomA, roomB) => {
return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned);
};
break;
case "manual":
default:
@ -236,6 +273,44 @@ class RoomListStore extends Store {
});
}
_updateCachedRoomState(roomId, type, value) {
const roomCache = this._state.roomCache;
if (!roomCache[roomId]) roomCache[roomId] = {};
if (value) roomCache[roomId][type] = value;
else delete roomCache[roomId][type];
this._setState({roomCache});
}
_clearCachedRoomState(roomId) {
const roomCache = this._state.roomCache;
delete roomCache[roomId];
this._setState({roomCache});
}
_getRoomState(room, type) {
const roomId = room.roomId;
const roomCache = this._state.roomCache;
if (roomCache[roomId] && typeof roomCache[roomId][type] !== 'undefined') {
return roomCache[roomId][type];
}
if (type === "timestamp") {
const ts = this._tsOfNewestEvent(room);
this._updateCachedRoomState(roomId, "timestamp", ts);
return ts;
} else if (type === "unread") {
const unread = room.getUnreadNotificationCount() > 0;
this._updateCachedRoomState(roomId, "unread", unread);
return unread;
} else if (type === "notifications") {
const notifs = room.getUnreadNotificationCount("highlight") > 0;
this._updateCachedRoomState(roomId, "notifications", notifs);
return notifs;
} else throw new Error("Unrecognized room cache type: " + type);
}
_eventTriggersRecentReorder(ev) {
return ev.getTs() && (
Unread.eventTriggersUnreadCount(ev) ||
@ -261,10 +336,40 @@ class RoomListStore extends Store {
}
}
_recentsComparator(roomA, roomB) {
// XXX: We could use a cache here and update it when we see new
// events that trigger a reorder
return this._tsOfNewestEvent(roomB) - this._tsOfNewestEvent(roomA);
_recentsComparator(roomA, roomB, pinUnread, pinMentioned) {
// We try and set the ordering to be Mentioned > Unread > Recent
// assuming the user has the right settings, of course.
const timestampA = this._getRoomState(roomA, "timestamp");
const timestampB = this._getRoomState(roomB, "timestamp");
const timestampDiff = timestampB - timestampA;
if (pinMentioned) {
const mentionsA = this._getRoomState(roomA, "notifications");
const mentionsB = this._getRoomState(roomB, "notifications");
if (mentionsA && !mentionsB) return -1;
if (!mentionsA && mentionsB) return 1;
// If they both have notifications, sort by timestamp.
// If neither have notifications (the fourth check not shown
// here), then try and sort by unread messages and finally by
// timestamp.
if (mentionsA && mentionsB) return timestampDiff;
}
if (pinUnread) {
const unreadA = this._getRoomState(roomA, "unread");
const unreadB = this._getRoomState(roomB, "unread");
if (unreadA && !unreadB) return -1;
if (!unreadA && unreadB) return 1;
// If they both have unread messages, sort by timestamp
// If nether have unread message (the fourth check not shown
// here), then just sort by timestamp anyways.
if (unreadA && unreadB) return timestampDiff;
}
return timestampDiff;
}
_lexicographicalComparator(roomA, roomB) {