Merge pull request #2575 from matrix-org/bwindels/customtags
Bring back custom tags, also badges on communities
This commit is contained in:
commit
87ddb8a453
18 changed files with 592 additions and 133 deletions
143
src/stores/CustomRoomTagStore.js
Normal file
143
src/stores/CustomRoomTagStore.js
Normal 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;
|
|
@ -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] || {};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue