Merge pull request #2575 from matrix-org/bwindels/customtags

Bring back custom tags, also badges on communities
This commit is contained in:
Bruno Windels 2019-02-07 11:37:17 +00:00 committed by GitHub
commit 87ddb8a453
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 592 additions and 133 deletions

View file

@ -0,0 +1,143 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import dis from '../dispatcher';
import * as RoomNotifs from '../RoomNotifs';
import RoomListStore from './RoomListStore';
import EventEmitter from 'events';
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
function commonPrefix(a, b) {
const len = Math.min(a.length, b.length);
let prefix;
for (let i = 0; i < len; ++i) {
if (a.charAt(i) !== b.charAt(i)) {
prefix = a.substr(0, i);
break;
}
}
if (prefix === undefined) {
prefix = a.substr(0, len);
}
const spaceIdx = prefix.indexOf(' ');
if (spaceIdx !== -1) {
prefix = prefix.substr(0, spaceIdx + 1);
}
if (prefix.length >= 2) {
return prefix;
}
return "";
}
/**
* A class for storing application state for ordering tags in the TagPanel.
*/
class CustomRoomTagStore extends EventEmitter {
constructor() {
super();
// Initialise state
this._state = {tags: this._getUpdatedTags()};
this._roomListStoreToken = RoomListStore.addListener(() => {
this._setState({tags: this._getUpdatedTags()});
});
dis.register(payload => this._onDispatch(payload));
}
getTags() {
return this._state.tags;
}
_setState(newState) {
this._state = Object.assign(this._state, newState);
this.emit("change");
}
addListener(callback) {
this.on("change", callback);
return {
remove: () => {
this.removeListener("change", callback);
},
};
}
getSortedTags() {
const roomLists = RoomListStore.getRoomLists();
const tagNames = Object.keys(this._state.tags).sort();
const prefixes = tagNames.map((name, i) => {
const isFirst = i === 0;
const isLast = i === tagNames.length - 1;
const backwardsPrefix = !isFirst ? commonPrefix(name, tagNames[i - 1]) : "";
const forwardsPrefix = !isLast ? commonPrefix(name, tagNames[i + 1]) : "";
const longestPrefix = backwardsPrefix.length > forwardsPrefix.length ?
backwardsPrefix : forwardsPrefix;
return longestPrefix;
});
return tagNames.map((name, i) => {
const notifs = RoomNotifs.aggregateNotificationCount(roomLists[name]);
let badge;
if (notifs.count !== 0) {
badge = notifs;
}
const avatarLetter = name.substr(prefixes[i].length, 1);
const selected = this._state.tags[name];
return {name, avatarLetter, badge, selected};
});
}
_onDispatch(payload) {
switch (payload.action) {
case 'select_custom_room_tag': {
const oldTags = this._state.tags;
if (oldTags.hasOwnProperty(payload.tag)) {
const tag = {};
tag[payload.tag] = !oldTags[payload.tag];
const tags = Object.assign({}, oldTags, tag);
this._setState({tags});
}
}
break;
case 'on_logged_out': {
this._state = {};
if (this._roomListStoreToken) {
this._roomListStoreToken.remove();
this._roomListStoreToken = null;
}
}
break;
}
}
_getUpdatedTags() {
const newTagNames = Object.keys(RoomListStore.getRoomLists())
.filter((tagName) => {
return !tagName.match(STANDARD_TAGS_REGEX);
}).sort();
const prevTags = this._state && this._state.tags;
const newTags = newTagNames.reduce((newTags, tagName) => {
newTags[tagName] = (prevTags && prevTags[tagName]) || false;
return newTags;
}, {});
return newTags;
}
}
if (global.singletonCustomRoomTagStore === undefined) {
global.singletonCustomRoomTagStore = new CustomRoomTagStore();
}
export default global.singletonCustomRoomTagStore;

View file

@ -203,6 +203,14 @@ class GroupStore extends EventEmitter {
return this._ready[id][groupId];
}
getGroupIdsForRoomId(roomId) {
const groupIds = Object.keys(this._state[this.STATE_KEY.GroupRooms]);
return groupIds.filter(groupId => {
const rooms = this._state[this.STATE_KEY.GroupRooms][groupId] || [];
return rooms.some(room => room.roomId === roomId);
});
}
getSummary(groupId) {
return this._state[this.STATE_KEY.Summary][groupId] || {};
}

View file

@ -224,9 +224,9 @@ class RoomListStore extends Store {
}
}
// ignore tags we don't know about
// ignore any m. tag names we don't know about
tagNames = tagNames.filter((t) => {
return lists[t] !== undefined;
return !t.startsWith('m.') || lists[t] !== undefined;
});
if (tagNames.length) {

View file

@ -15,7 +15,10 @@ limitations under the License.
*/
import {Store} from 'flux/utils';
import dis from '../dispatcher';
import GroupStore from './GroupStore';
import Analytics from '../Analytics';
import * as RoomNotifs from "../RoomNotifs";
import MatrixClientPeg from '../MatrixClientPeg';
const INITIAL_STATE = {
orderedTags: null,
@ -47,7 +50,15 @@ class TagOrderStore extends Store {
__onDispatch(payload) {
switch (payload.action) {
// Initialise state after initial sync
case 'view_room': {
const relatedGroupIds = GroupStore.getGroupIdsForRoomId(payload.room_id);
this._updateBadges(relatedGroupIds);
break;
}
case 'MatrixActions.sync': {
if (payload.state === 'SYNCING' || payload.state === 'PREPARED') {
this._updateBadges();
}
if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) {
break;
}
@ -164,6 +175,20 @@ class TagOrderStore extends Store {
}
}
_updateBadges(groupIds = this._state.joinedGroupIds) {
if (groupIds && groupIds.length) {
const client = MatrixClientPeg.get();
const changedBadges = {};
groupIds.forEach(groupId => {
const rooms = GroupStore.getGroupRooms(groupId).map(r => client.getRoom(r.roomId));
const badge = rooms && RoomNotifs.aggregateNotificationCount(rooms);
changedBadges[groupId] = (badge && badge.count !== 0) ? badge : undefined;
});
const newBadges = Object.assign({}, this._state.badges, changedBadges);
this._setState({badges: newBadges});
}
}
_updateOrderedTags() {
this._setState({
orderedTags:
@ -190,6 +215,11 @@ class TagOrderStore extends Store {
return tagsToKeep.concat(groupIdsToAdd);
}
getGroupBadge(groupId) {
const badges = this._state.badges;
return badges && badges[groupId];
}
getOrderedTags() {
return this._state.orderedTags;
}