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
|
@ -91,6 +91,7 @@ module.exports = {
|
||||||
// to JSX.
|
// to JSX.
|
||||||
ignorePattern: '^\\s*<',
|
ignorePattern: '^\\s*<',
|
||||||
ignoreComments: true,
|
ignoreComments: true,
|
||||||
|
ignoreRegExpLiterals: true,
|
||||||
code: 120,
|
code: 120,
|
||||||
}],
|
}],
|
||||||
"valid-jsdoc": ["warn"],
|
"valid-jsdoc": ["warn"],
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
@import "./structures/_CompatibilityPage.scss";
|
@import "./structures/_CompatibilityPage.scss";
|
||||||
@import "./structures/_ContextualMenu.scss";
|
@import "./structures/_ContextualMenu.scss";
|
||||||
@import "./structures/_CreateRoom.scss";
|
@import "./structures/_CreateRoom.scss";
|
||||||
|
@import "./structures/_CustomRoomTagPanel.scss";
|
||||||
@import "./structures/_FilePanel.scss";
|
@import "./structures/_FilePanel.scss";
|
||||||
@import "./structures/_GroupView.scss";
|
@import "./structures/_GroupView.scss";
|
||||||
@import "./structures/_HomePage.scss";
|
@import "./structures/_HomePage.scss";
|
||||||
|
@ -19,6 +20,7 @@
|
||||||
@import "./structures/_SearchBox.scss";
|
@import "./structures/_SearchBox.scss";
|
||||||
@import "./structures/_TabbedView.scss";
|
@import "./structures/_TabbedView.scss";
|
||||||
@import "./structures/_TagPanel.scss";
|
@import "./structures/_TagPanel.scss";
|
||||||
|
@import "./structures/_TagPanelButtons.scss";
|
||||||
@import "./structures/_TopLeftMenuButton.scss";
|
@import "./structures/_TopLeftMenuButton.scss";
|
||||||
@import "./structures/_UploadBar.scss";
|
@import "./structures/_UploadBar.scss";
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
|
|
41
res/css/structures/_CustomRoomTagPanel.scss
Normal file
41
res/css/structures/_CustomRoomTagPanel.scss
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_LeftPanel_tagPanelContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CustomRoomTagPanel {
|
||||||
|
background-color: $tagpanel-bg-color;
|
||||||
|
max-height: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CustomRoomTagPanel .mx_AccessibleButton {
|
||||||
|
margin: 9px auto;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CustomRoomTagPanel .mx_BaseAvatar_image {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CustomRoomTagPanel .mx_AccessibleButton.CustomRoomTagPanel_tileSelected .mx_BaseAvatar_image {
|
||||||
|
border: 3px solid $warning-color;
|
||||||
|
border-radius: 40px;
|
||||||
|
}
|
|
@ -33,6 +33,11 @@ limitations under the License.
|
||||||
flex: 0 0 140px;
|
flex: 0 0 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_LeftPanel_tagPanelContainer {
|
||||||
|
flex: 0 0 70px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_LeftPanel_hideButton {
|
.mx_LeftPanel_hideButton {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_TagPanel {
|
.mx_TagPanel {
|
||||||
flex: 0 0 70px;
|
flex: 1;
|
||||||
background-color: $tagpanel-bg-color;
|
background-color: $tagpanel-bg-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -68,10 +68,13 @@ limitations under the License.
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
||||||
|
height: 40px;
|
||||||
|
padding: 5px 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile {
|
.mx_TagPanel .mx_TagTile {
|
||||||
padding-top: 9px;
|
margin: 9px 0;
|
||||||
padding-bottom: 9px;
|
|
||||||
// opacity: 0.5;
|
// opacity: 0.5;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -81,13 +84,7 @@ limitations under the License.
|
||||||
// opacity: 1;
|
// opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
|
|
||||||
/* To offset border of mx_TagTile_avatar */
|
|
||||||
padding: 3px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {
|
.mx_TagPanel .mx_TagTile.mx_TagTile_selected .mx_TagTile_avatar .mx_BaseAvatar {
|
||||||
border: 3px solid $accent-color;
|
|
||||||
background-color: $accent-color;
|
background-color: $accent-color;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
|
|
||||||
|
@ -97,6 +94,13 @@ limitations under the License.
|
||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagPanel .mx_TagTile_selected .mx_BaseAvatar_image {
|
||||||
|
border: 3px solid $accent-color;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +116,7 @@ limitations under the License.
|
||||||
height: 15px;
|
height: 15px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
top: 1px;
|
top: -8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: $neutral-badge-color;
|
background-color: $neutral-badge-color;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
@ -124,39 +128,22 @@ limitations under the License.
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel_groupsButton {
|
.mx_TagTile_avatar {
|
||||||
flex: 0;
|
|
||||||
margin: 17px 0 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel_groupsButton > .mx_GroupsButton:before {
|
|
||||||
mask: url('$(res)/img/feather-icons/users.svg');
|
|
||||||
mask-position: center 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel_groupsButton > .mx_TagPanel_report:before {
|
|
||||||
mask: url('$(res)/img/feather-icons/life-buoy.svg');
|
|
||||||
mask-position: center 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel_groupsButton > .mx_AccessibleButton {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
background-color: $roomheader-addroom-color;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
/* overwrite mx_RoleButton inline-block */
|
}
|
||||||
display: block !important;
|
|
||||||
|
|
||||||
&:before {
|
.mx_TagTile_badge {
|
||||||
background-color: $tagpanel-bg-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
right: -4px;
|
||||||
bottom: 0;
|
top: -2px;
|
||||||
left: 0;
|
border-radius: 8px;
|
||||||
right: 0;
|
color: $accent-fg-color;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 5px;
|
||||||
|
background-color: $roomtile-name-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagTile_badgeHighlight {
|
||||||
|
background-color: $warning-color;
|
||||||
}
|
}
|
||||||
|
|
56
res/css/structures/_TagPanelButtons.scss
Normal file
56
res/css/structures/_TagPanelButtons.scss
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_TagPanelButtons {
|
||||||
|
background-color: $tagpanel-bg-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 17px 0 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagPanelButtons > .mx_GroupsButton:before {
|
||||||
|
mask: url('$(res)/img/feather-icons/users.svg');
|
||||||
|
mask-position: center 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagPanelButtons > .mx_TagPanelButtons_report:before {
|
||||||
|
mask: url('$(res)/img/feather-icons/life-buoy.svg');
|
||||||
|
mask-position: center 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagPanelButtons > .mx_AccessibleButton {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: $roomheader-addroom-color;
|
||||||
|
position: relative;
|
||||||
|
/* overwrite mx_RoleButton inline-block */
|
||||||
|
display: block !important;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
background-color: $tagpanel-bg-color;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,47 @@ export const ALL_MESSAGES = 'all_messages';
|
||||||
export const MENTIONS_ONLY = 'mentions_only';
|
export const MENTIONS_ONLY = 'mentions_only';
|
||||||
export const MUTE = 'mute';
|
export const MUTE = 'mute';
|
||||||
|
|
||||||
|
|
||||||
|
function _shouldShowNotifBadge(roomNotifState) {
|
||||||
|
const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD];
|
||||||
|
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _shouldShowMentionBadge(roomNotifState) {
|
||||||
|
return roomNotifState !== MUTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aggregateNotificationCount(rooms) {
|
||||||
|
return rooms.reduce((result, room, index) => {
|
||||||
|
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||||
|
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||||
|
const notificationCount = room.getUnreadNotificationCount();
|
||||||
|
|
||||||
|
const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState);
|
||||||
|
const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState);
|
||||||
|
const badges = notifBadges || mentionBadges;
|
||||||
|
|
||||||
|
if (badges) {
|
||||||
|
result.count += notificationCount;
|
||||||
|
if (highlight) {
|
||||||
|
result.highlight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, {count: 0, highlight: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRoomHasBadge(room) {
|
||||||
|
const roomNotifState = getRoomNotifsState(room.roomId);
|
||||||
|
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
||||||
|
const notificationCount = room.getUnreadNotificationCount();
|
||||||
|
|
||||||
|
const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState);
|
||||||
|
const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState);
|
||||||
|
|
||||||
|
return notifBadges || mentionBadges;
|
||||||
|
}
|
||||||
|
|
||||||
export function getRoomNotifsState(roomId) {
|
export function getRoomNotifsState(roomId) {
|
||||||
if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES;
|
if (MatrixClientPeg.get().isGuest()) return ALL_MESSAGES;
|
||||||
|
|
||||||
|
|
125
src/components/structures/CustomRoomTagPanel.js
Normal file
125
src/components/structures/CustomRoomTagPanel.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
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 React from 'react';
|
||||||
|
import CustomRoomTagStore from '../../stores/CustomRoomTagStore';
|
||||||
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
|
import sdk from '../../index';
|
||||||
|
import dis from '../../dispatcher';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||||
|
|
||||||
|
class CustomRoomTagPanel extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
tags: CustomRoomTagStore.getSortedTags(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._tagStoreToken = CustomRoomTagStore.addListener(() => {
|
||||||
|
this.setState({tags: CustomRoomTagStore.getSortedTags()});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this._tagStoreToken) {
|
||||||
|
this._tagStoreToken.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const tags = this.state.tags.map((tag) => {
|
||||||
|
return (<CustomRoomTagTile tag={tag} key={tag.name} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const classes = classNames('mx_CustomRoomTagPanel', {
|
||||||
|
mx_CustomRoomTagPanel_empty: this.state.tags.length === 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (<div className={classes}>
|
||||||
|
<div className="mx_CustomRoomTagPanel_divider" />
|
||||||
|
<AutoHideScrollbar className="mx_CustomRoomTagPanel_scroller">
|
||||||
|
{tags}
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomRoomTagTile extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {hover: false};
|
||||||
|
this.onClick = this.onClick.bind(this);
|
||||||
|
this.onMouseOut = this.onMouseOut.bind(this);
|
||||||
|
this.onMouseOver = this.onMouseOver.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOver() {
|
||||||
|
this.setState({hover: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOut() {
|
||||||
|
this.setState({hover: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
dis.dispatch({action: 'select_custom_room_tag', tag: this.props.tag.name});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
const RoomTooltip = sdk.getComponent('rooms.RoomTooltip');
|
||||||
|
|
||||||
|
const tag = this.props.tag;
|
||||||
|
const avatarHeight = 40;
|
||||||
|
const className = classNames({
|
||||||
|
CustomRoomTagPanel_tileSelected: tag.selected,
|
||||||
|
});
|
||||||
|
const name = tag.name;
|
||||||
|
const badge = tag.badge;
|
||||||
|
let badgeElement;
|
||||||
|
if (badge) {
|
||||||
|
const badgeClasses = classNames({
|
||||||
|
"mx_TagTile_badge": true,
|
||||||
|
"mx_TagTile_badgeHighlight": badge.highlight,
|
||||||
|
});
|
||||||
|
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tip = (this.state.hover ?
|
||||||
|
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||||
|
<div />);
|
||||||
|
return (
|
||||||
|
<AccessibleButton className={className} onClick={this.onClick}>
|
||||||
|
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||||
|
<BaseAvatar
|
||||||
|
name={tag.avatarLetter}
|
||||||
|
idName={name}
|
||||||
|
width={avatarHeight}
|
||||||
|
height={avatarHeight}
|
||||||
|
/>
|
||||||
|
{ badgeElement }
|
||||||
|
{ tip }
|
||||||
|
</div>
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomRoomTagPanel;
|
|
@ -24,7 +24,7 @@ import { KeyCode } from '../../Keyboard';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
import VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||||
|
import TagPanelButtons from './TagPanelButtons';
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
|
||||||
|
|
||||||
|
@ -183,12 +183,20 @@ const LeftPanel = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const RoomList = sdk.getComponent('rooms.RoomList');
|
const RoomList = sdk.getComponent('rooms.RoomList');
|
||||||
const TagPanel = sdk.getComponent('structures.TagPanel');
|
const TagPanel = sdk.getComponent('structures.TagPanel');
|
||||||
|
const CustomRoomTagPanel = sdk.getComponent('structures.CustomRoomTagPanel');
|
||||||
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
const TopLeftMenuButton = sdk.getComponent('structures.TopLeftMenuButton');
|
||||||
const SearchBox = sdk.getComponent('structures.SearchBox');
|
const SearchBox = sdk.getComponent('structures.SearchBox');
|
||||||
const CallPreview = sdk.getComponent('voip.CallPreview');
|
const CallPreview = sdk.getComponent('voip.CallPreview');
|
||||||
|
|
||||||
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
|
const tagPanelEnabled = SettingsStore.getValue("TagPanel.enableTagPanel");
|
||||||
const tagPanel = tagPanelEnabled ? <TagPanel /> : <div />;
|
let tagPanelContainer;
|
||||||
|
if (tagPanelEnabled) {
|
||||||
|
tagPanelContainer = (<div className="mx_LeftPanel_tagPanelContainer">
|
||||||
|
<TagPanel />
|
||||||
|
<CustomRoomTagPanel />
|
||||||
|
<TagPanelButtons />
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
const containerClasses = classNames(
|
const containerClasses = classNames(
|
||||||
"mx_LeftPanel_container", "mx_fadable",
|
"mx_LeftPanel_container", "mx_fadable",
|
||||||
|
@ -204,9 +212,10 @@ const LeftPanel = React.createClass({
|
||||||
onCleared={ this.onSearchCleared }
|
onCleared={ this.onSearchCleared }
|
||||||
collapsed={this.props.collapsed} />);
|
collapsed={this.props.collapsed} />);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{ tagPanel }
|
{ tagPanelContainer }
|
||||||
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
<aside className={"mx_LeftPanel dark-panel"} onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
|
||||||
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
<TopLeftMenuButton collapsed={ this.props.collapsed } />
|
||||||
{ searchBox }
|
{ searchBox }
|
||||||
|
|
|
@ -127,46 +127,6 @@ const RoomSubList = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_shouldShowNotifBadge: function(roomNotifState) {
|
|
||||||
const showBadgeInStates = [RoomNotifs.ALL_MESSAGES, RoomNotifs.ALL_MESSAGES_LOUD];
|
|
||||||
return showBadgeInStates.indexOf(roomNotifState) > -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
_shouldShowMentionBadge: function(roomNotifState) {
|
|
||||||
return roomNotifState !== RoomNotifs.MUTE;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Total up all the notification counts from the rooms
|
|
||||||
*
|
|
||||||
* @returns {Array} The array takes the form [total, highlight] where highlight is a bool
|
|
||||||
*/
|
|
||||||
roomNotificationCount: function() {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
if (this.props.isInvite) {
|
|
||||||
return [0, true];
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.list.reduce(function(result, room, index) {
|
|
||||||
const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
|
||||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
|
||||||
const notificationCount = room.getUnreadNotificationCount();
|
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && self._shouldShowNotifBadge(roomNotifState);
|
|
||||||
const mentionBadges = highlight && self._shouldShowMentionBadge(roomNotifState);
|
|
||||||
const badges = notifBadges || mentionBadges;
|
|
||||||
|
|
||||||
if (badges) {
|
|
||||||
result[0] += notificationCount;
|
|
||||||
if (highlight) {
|
|
||||||
result[1] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, [0, false]);
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateSubListCount: function() {
|
_updateSubListCount: function() {
|
||||||
// Force an update by setting the state to the current state
|
// Force an update by setting the state to the current state
|
||||||
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
// Doing it this way rather than using forceUpdate(), so that the shouldComponentUpdate()
|
||||||
|
@ -197,22 +157,12 @@ const RoomSubList = React.createClass({
|
||||||
// prevent the roomsublist collapsing
|
// prevent the roomsublist collapsing
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// find first room which has notifications and switch to it
|
const room = this.props.lists.find(room => RoomNotifs.getRoomHasBadge(room));
|
||||||
for (const room of this.props.list) {
|
if (room) {
|
||||||
const roomNotifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
|
||||||
const highlight = room.getUnreadNotificationCount('highlight') > 0;
|
|
||||||
const notificationCount = room.getUnreadNotificationCount();
|
|
||||||
|
|
||||||
const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(roomNotifState);
|
|
||||||
const mentionBadges = highlight && this._shouldShowMentionBadge(roomNotifState);
|
|
||||||
|
|
||||||
if (notifBadges || mentionBadges) {
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_id: room.roomId,
|
room_id: room.roomId,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -240,9 +190,11 @@ const RoomSubList = React.createClass({
|
||||||
|
|
||||||
_getHeaderJsx: function(isCollapsed) {
|
_getHeaderJsx: function(isCollapsed) {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const subListNotifications = this.roomNotificationCount();
|
const subListNotifications = !this.props.isInvite ?
|
||||||
const subListNotifCount = subListNotifications[0];
|
RoomNotifs.aggregateNotificationCount(this.props.list) :
|
||||||
const subListNotifHighlight = subListNotifications[1];
|
{count: 0, highlight: true};
|
||||||
|
const subListNotifCount = subListNotifications.count;
|
||||||
|
const subListNotifHighlight = subListNotifications.highlight;
|
||||||
|
|
||||||
let badge;
|
let badge;
|
||||||
if (!this.props.collapsed) {
|
if (!this.props.collapsed) {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import GroupActions from '../../actions/GroupActions';
|
||||||
|
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import Modal from '../../Modal';
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
import { Droppable } from 'react-beautiful-dnd';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
|
@ -48,8 +47,6 @@ const TagPanel = React.createClass({
|
||||||
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
this.context.matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||||
this.context.matrixClient.on("sync", this._onClientSync);
|
this.context.matrixClient.on("sync", this._onClientSync);
|
||||||
|
|
||||||
this._dispatcherRef = dis.register(this._onAction);
|
|
||||||
|
|
||||||
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
this._tagOrderStoreToken = TagOrderStore.addListener(() => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return;
|
||||||
|
@ -70,9 +67,6 @@ const TagPanel = React.createClass({
|
||||||
if (this._filterStoreToken) {
|
if (this._filterStoreToken) {
|
||||||
this._filterStoreToken.remove();
|
this._filterStoreToken.remove();
|
||||||
}
|
}
|
||||||
if (this._dispatcherRef) {
|
|
||||||
dis.unregister(this._dispatcherRef);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_onGroupMyMembership() {
|
_onGroupMyMembership() {
|
||||||
|
@ -106,21 +100,11 @@ const TagPanel = React.createClass({
|
||||||
dis.dispatch({action: 'deselect_tags'});
|
dis.dispatch({action: 'deselect_tags'});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onAction(payload) {
|
|
||||||
if (payload.action === "show_redesign_feedback_dialog") {
|
|
||||||
const RedesignFeedbackDialog =
|
|
||||||
sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
|
||||||
Modal.createDialog(RedesignFeedbackDialog);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const GroupsButton = sdk.getComponent('elements.GroupsButton');
|
|
||||||
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
|
||||||
const ActionButton = sdk.getComponent("elements.ActionButton");
|
|
||||||
|
|
||||||
const tags = this.state.orderedTags.map((tag, index) => {
|
const tags = this.state.orderedTags.map((tag, index) => {
|
||||||
return <DNDTagTile
|
return <DNDTagTile
|
||||||
|
@ -174,13 +158,6 @@ const TagPanel = React.createClass({
|
||||||
) }
|
) }
|
||||||
</Droppable>
|
</Droppable>
|
||||||
</GeminiScrollbarWrapper>
|
</GeminiScrollbarWrapper>
|
||||||
<div className="mx_TagPanel_divider" />
|
|
||||||
<div className="mx_TagPanel_groupsButton">
|
|
||||||
<GroupsButton />
|
|
||||||
<ActionButton
|
|
||||||
className="mx_TagPanel_report" action="show_redesign_feedback_dialog"
|
|
||||||
label={_t("Report bugs & give feedback")} tooltip={true} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
58
src/components/structures/TagPanelButtons.js
Normal file
58
src/components/structures/TagPanelButtons.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
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 React from 'react';
|
||||||
|
import sdk from '../../index';
|
||||||
|
import dis from '../../dispatcher';
|
||||||
|
import Modal from '../../Modal';
|
||||||
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
|
const TagPanelButtons = React.createClass({
|
||||||
|
displayName: 'TagPanelButtons',
|
||||||
|
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._dispatcherRef = dis.register(this._onAction);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this._dispatcherRef) {
|
||||||
|
dis.unregister(this._dispatcherRef);
|
||||||
|
this._dispatcherRef = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onAction(payload) {
|
||||||
|
if (payload.action === "show_redesign_feedback_dialog") {
|
||||||
|
const RedesignFeedbackDialog =
|
||||||
|
sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
|
||||||
|
Modal.createDialog(RedesignFeedbackDialog);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const GroupsButton = sdk.getComponent('elements.GroupsButton');
|
||||||
|
const ActionButton = sdk.getComponent("elements.ActionButton");
|
||||||
|
|
||||||
|
return (<div className="mx_TagPanelButtons">
|
||||||
|
<GroupsButton />
|
||||||
|
<ActionButton
|
||||||
|
className="mx_TagPanelButtons_report" action="show_redesign_feedback_dialog"
|
||||||
|
label={_t("Report bugs & give feedback")} tooltip={true} />
|
||||||
|
</div>);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export default TagPanelButtons;
|
|
@ -23,9 +23,11 @@ import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||||
import * as ContextualMenu from '../../structures/ContextualMenu';
|
import * as ContextualMenu from '../../structures/ContextualMenu';
|
||||||
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
|
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
|
|
||||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||||
|
@ -168,6 +170,16 @@ export default React.createClass({
|
||||||
mx_TagTile_selected: this.props.selected,
|
mx_TagTile_selected: this.props.selected,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const badge = TagOrderStore.getGroupBadge(this.props.tag);
|
||||||
|
let badgeElement;
|
||||||
|
if (badge && !this.state.hover) {
|
||||||
|
const badgeClasses = classNames({
|
||||||
|
"mx_TagTile_badge": true,
|
||||||
|
"mx_TagTile_badgeHighlight": badge.highlight,
|
||||||
|
});
|
||||||
|
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||||
|
}
|
||||||
|
|
||||||
const tip = this.state.hover ?
|
const tip = this.state.hover ?
|
||||||
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
<RoomTooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||||
<div />;
|
<div />;
|
||||||
|
@ -186,6 +198,7 @@ export default React.createClass({
|
||||||
/>
|
/>
|
||||||
{ tip }
|
{ tip }
|
||||||
{ contextButton }
|
{ contextButton }
|
||||||
|
{ badgeElement }
|
||||||
</div>
|
</div>
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,11 +32,12 @@ import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
const Receipt = require('../../../utils/Receipt');
|
const Receipt = require('../../../utils/Receipt');
|
||||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
import RoomListStore from '../../../stores/RoomListStore';
|
import RoomListStore from '../../../stores/RoomListStore';
|
||||||
|
import CustomRoomTagStore from '../../../stores/CustomRoomTagStore';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import RoomSubList from '../../structures/RoomSubList';
|
import RoomSubList from '../../structures/RoomSubList';
|
||||||
import ResizeHandle from '../elements/ResizeHandle';
|
import ResizeHandle from '../elements/ResizeHandle';
|
||||||
|
|
||||||
import {Resizer} from '../../../resizer'
|
import {Resizer} from '../../../resizer';
|
||||||
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
import {Layout, Distributor} from '../../../resizer/distributors/roomsublist2';
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
const STANDARD_TAGS_REGEX = /^(m\.(favourite|lowpriority|server_notice)|im\.vector\.fake\.(invite|recent|direct|archived))$/;
|
||||||
|
@ -121,6 +122,7 @@ module.exports = React.createClass({
|
||||||
incomingCall: null,
|
incomingCall: null,
|
||||||
selectedTags: [],
|
selectedTags: [],
|
||||||
hover: false,
|
hover: false,
|
||||||
|
customTags: CustomRoomTagStore.getTags(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -170,6 +172,12 @@ module.exports = React.createClass({
|
||||||
this._delayedRefreshRoomList();
|
this._delayedRefreshRoomList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._customTagStoreToken = CustomRoomTagStore.addListener(() => {
|
||||||
|
this.setState({
|
||||||
|
customTags: CustomRoomTagStore.getTags(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
|
|
||||||
// order of the sublists
|
// order of the sublists
|
||||||
|
@ -266,6 +274,9 @@ module.exports = React.createClass({
|
||||||
if (this._roomListStoreToken) {
|
if (this._roomListStoreToken) {
|
||||||
this._roomListStoreToken.remove();
|
this._roomListStoreToken.remove();
|
||||||
}
|
}
|
||||||
|
if (this._customTagStoreToken) {
|
||||||
|
this._customTagStoreToken.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// NB: GroupStore is not a Flux.Store
|
// NB: GroupStore is not a Flux.Store
|
||||||
if (this._groupStoreToken) {
|
if (this._groupStoreToken) {
|
||||||
|
@ -717,7 +728,7 @@ module.exports = React.createClass({
|
||||||
];
|
];
|
||||||
const tagSubLists = Object.keys(this.state.lists)
|
const tagSubLists = Object.keys(this.state.lists)
|
||||||
.filter((tagName) => {
|
.filter((tagName) => {
|
||||||
return !tagName.match(STANDARD_TAGS_REGEX);
|
return this.state.customTags[tagName] && !tagName.match(STANDARD_TAGS_REGEX);
|
||||||
}).map((tagName) => {
|
}).map((tagName) => {
|
||||||
return {
|
return {
|
||||||
list: this.state.lists[tagName],
|
list: this.state.lists[tagName],
|
||||||
|
|
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];
|
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) {
|
getSummary(groupId) {
|
||||||
return this._state[this.STATE_KEY.Summary][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) => {
|
tagNames = tagNames.filter((t) => {
|
||||||
return lists[t] !== undefined;
|
return !t.startsWith('m.') || lists[t] !== undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
|
|
|
@ -15,7 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
import {Store} from 'flux/utils';
|
import {Store} from 'flux/utils';
|
||||||
import dis from '../dispatcher';
|
import dis from '../dispatcher';
|
||||||
|
import GroupStore from './GroupStore';
|
||||||
import Analytics from '../Analytics';
|
import Analytics from '../Analytics';
|
||||||
|
import * as RoomNotifs from "../RoomNotifs";
|
||||||
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
orderedTags: null,
|
orderedTags: null,
|
||||||
|
@ -47,7 +50,15 @@ class TagOrderStore extends Store {
|
||||||
__onDispatch(payload) {
|
__onDispatch(payload) {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
// Initialise state after initial sync
|
// Initialise state after initial sync
|
||||||
|
case 'view_room': {
|
||||||
|
const relatedGroupIds = GroupStore.getGroupIdsForRoomId(payload.room_id);
|
||||||
|
this._updateBadges(relatedGroupIds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'MatrixActions.sync': {
|
case 'MatrixActions.sync': {
|
||||||
|
if (payload.state === 'SYNCING' || payload.state === 'PREPARED') {
|
||||||
|
this._updateBadges();
|
||||||
|
}
|
||||||
if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) {
|
if (!(payload.prevState !== 'PREPARED' && payload.state === 'PREPARED')) {
|
||||||
break;
|
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() {
|
_updateOrderedTags() {
|
||||||
this._setState({
|
this._setState({
|
||||||
orderedTags:
|
orderedTags:
|
||||||
|
@ -190,6 +215,11 @@ class TagOrderStore extends Store {
|
||||||
return tagsToKeep.concat(groupIdsToAdd);
|
return tagsToKeep.concat(groupIdsToAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroupBadge(groupId) {
|
||||||
|
const badges = this._state.badges;
|
||||||
|
return badges && badges[groupId];
|
||||||
|
}
|
||||||
|
|
||||||
getOrderedTags() {
|
getOrderedTags() {
|
||||||
return this._state.orderedTags;
|
return this._state.orderedTags;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue