mirror of
https://github.com/element-hq/element-web
synced 2024-11-24 02:05:45 +03:00
Merge branch 'develop' into travis/breadcrumbs/telemetry
This commit is contained in:
commit
e2edae3383
8 changed files with 168 additions and 39 deletions
|
@ -19,10 +19,15 @@ limitations under the License.
|
|||
height: 42px;
|
||||
padding: 8px;
|
||||
padding-bottom: 0;
|
||||
overflow-x: visible;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
// Autohide the scrollbar
|
||||
overflow-x: hidden;
|
||||
&:hover {
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
.mx_AutoHideScrollbar_offset {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -41,6 +46,12 @@ limitations under the License.
|
|||
top: -3px;
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_dmIndicator {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RoomBreadcrumbs_animate {
|
||||
|
|
|
@ -40,6 +40,13 @@ export default class IndicatorScrollbar extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
moveToOrigin() {
|
||||
if (!this._scrollElement) return;
|
||||
|
||||
this._scrollElement.scrollLeft = 0;
|
||||
this._scrollElement.scrollTop = 0;
|
||||
}
|
||||
|
||||
_collectScroller(scroller) {
|
||||
if (scroller && !this._scrollElement) {
|
||||
this._scrollElement = scroller;
|
||||
|
|
|
@ -536,7 +536,9 @@ module.exports = React.createClass({
|
|||
payload.data.description || payload.data.name);
|
||||
break;
|
||||
case 'picture_snapshot':
|
||||
this.uploadFile(payload.file);
|
||||
return ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
[payload.file], this.state.room.roomId, MatrixClientPeg.get(),
|
||||
);
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
case 'upload_started':
|
||||
|
|
|
@ -47,7 +47,7 @@ import {Completion} from "../../../autocomplete/Autocompleter";
|
|||
import Markdown from '../../../Markdown';
|
||||
import ComposerHistoryManager from '../../../ComposerHistoryManager';
|
||||
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
||||
import ContentMessage from '../../../ContentMessages';
|
||||
import ContentMessages from '../../../ContentMessages';
|
||||
|
||||
import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';
|
||||
|
||||
|
@ -139,8 +139,6 @@ export default class MessageComposerInput extends React.Component {
|
|||
// js-sdk Room object
|
||||
room: PropTypes.object.isRequired,
|
||||
|
||||
onFilesPasted: PropTypes.func,
|
||||
|
||||
onInputStateChanged: PropTypes.func,
|
||||
};
|
||||
|
||||
|
@ -1014,7 +1012,7 @@ export default class MessageComposerInput extends React.Component {
|
|||
// neither chrome nor firefox let you paste a plain file copied
|
||||
// from Finder) but more images copied from a different website
|
||||
// / word processor etc.
|
||||
return ContentMessage.sharedInstance().sendContentListToRoom(
|
||||
return ContentMessages.sharedInstance().sendContentListToRoom(
|
||||
transfer.files, this.props.room.roomId, this.client,
|
||||
);
|
||||
case 'html': {
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import React from "react";
|
||||
import dis from "../../../dispatcher";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import RoomAvatar from '../avatars/RoomAvatar';
|
||||
import classNames from 'classnames';
|
||||
|
@ -25,6 +25,8 @@ import sdk from "../../../index";
|
|||
import Analytics from "../../../Analytics";
|
||||
import * as RoomNotifs from '../../../RoomNotifs';
|
||||
import * as FormattingUtils from "../../../utils/FormattingUtils";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import {_t} from "../../../languageHandler";
|
||||
|
||||
const MAX_ROOMS = 20;
|
||||
|
||||
|
@ -39,22 +41,22 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
componentWillMount() {
|
||||
this._dispatcherRef = dis.register(this.onAction);
|
||||
|
||||
const roomStr = localStorage.getItem("mx_breadcrumb_rooms");
|
||||
if (roomStr) {
|
||||
try {
|
||||
const roomIds = JSON.parse(roomStr);
|
||||
this.setState({
|
||||
rooms: roomIds.map((r) => {
|
||||
return {
|
||||
room: MatrixClientPeg.get().getRoom(r),
|
||||
animated: false,
|
||||
};
|
||||
}).filter((r) => r.room),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to parse breadcrumbs:", e);
|
||||
let storedRooms = SettingsStore.getValue("breadcrumb_rooms");
|
||||
if (!storedRooms || !storedRooms.length) {
|
||||
// Fallback to the rooms stored in localstorage for those who would have had this.
|
||||
// TODO: Remove this after a bit - the feature was only on develop, so a few weeks should be plenty time.
|
||||
const roomStr = localStorage.getItem("mx_breadcrumb_rooms");
|
||||
if (roomStr) {
|
||||
try {
|
||||
storedRooms = JSON.parse(roomStr);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse breadcrumbs:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._loadRoomIds(storedRooms || []);
|
||||
|
||||
this._settingWatchRef = SettingsStore.watchSetting("breadcrumb_rooms", null, this.onBreadcrumbsChanged);
|
||||
|
||||
MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership);
|
||||
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||
|
@ -65,6 +67,8 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
componentWillUnmount() {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
|
||||
SettingsStore.unwatchSetting(this._settingWatchRef);
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
if (client) {
|
||||
client.removeListener("Room.myMembership", this.onMyMembership);
|
||||
|
@ -78,15 +82,17 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
const rooms = this.state.rooms.slice();
|
||||
|
||||
if (rooms.length) {
|
||||
const {room, animated} = rooms[0];
|
||||
if (!animated) {
|
||||
rooms[0] = {room, animated: true};
|
||||
const roomModel = rooms[0];
|
||||
if (!roomModel.animated) {
|
||||
roomModel.animated = true;
|
||||
setTimeout(() => this.setState({rooms}), 0);
|
||||
}
|
||||
}
|
||||
|
||||
const roomStr = JSON.stringify(rooms.map((r) => r.room.roomId));
|
||||
localStorage.setItem("mx_breadcrumb_rooms", roomStr);
|
||||
const roomIds = rooms.map((r) => r.room.roomId);
|
||||
if (roomIds.length > 0) {
|
||||
SettingsStore.setValue("breadcrumb_rooms", null, SettingLevel.ACCOUNT, roomIds);
|
||||
}
|
||||
}
|
||||
|
||||
onAction(payload) {
|
||||
|
@ -126,17 +132,50 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
_calculateRoomBadges(room) {
|
||||
if (!room) return;
|
||||
onBreadcrumbsChanged = (settingName, roomId, level, valueAtLevel, value) => {
|
||||
if (!value) return;
|
||||
|
||||
const rooms = this.state.rooms.slice();
|
||||
const roomModel = rooms.find((r) => r.room.roomId === room.roomId);
|
||||
if (!roomModel) return; // No applicable room, so don't do math on it
|
||||
const currentState = this.state.rooms.map((r) => r.room.roomId);
|
||||
if (currentState.length === value.length) {
|
||||
let changed = false;
|
||||
for (let i = 0; i < currentState.length; i++) {
|
||||
if (currentState[i] !== value[i]) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!changed) return;
|
||||
}
|
||||
|
||||
this._loadRoomIds(value);
|
||||
};
|
||||
|
||||
_loadRoomIds(roomIds) {
|
||||
if (!roomIds || roomIds.length <= 0) return; // Skip updates with no rooms
|
||||
|
||||
// If we're here, the list changed.
|
||||
const rooms = roomIds.map((r) => MatrixClientPeg.get().getRoom(r)).filter((r) => r).map((r) => {
|
||||
const badges = this._calculateBadgesForRoom(r) || {};
|
||||
return {
|
||||
room: r,
|
||||
animated: false,
|
||||
...badges,
|
||||
};
|
||||
});
|
||||
this.setState({
|
||||
rooms: rooms,
|
||||
});
|
||||
}
|
||||
|
||||
_calculateBadgesForRoom(room) {
|
||||
if (!room) return null;
|
||||
|
||||
// Reset the notification variables for simplicity
|
||||
roomModel.redBadge = false;
|
||||
roomModel.formattedCount = "0";
|
||||
roomModel.showCount = false;
|
||||
const roomModel = {
|
||||
redBadge: false,
|
||||
formattedCount: "0",
|
||||
showCount: false,
|
||||
};
|
||||
|
||||
const notifState = RoomNotifs.getRoomNotifsState(room.roomId);
|
||||
if (RoomNotifs.MENTION_BADGE_STATES.includes(notifState)) {
|
||||
|
@ -156,24 +195,57 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
return roomModel;
|
||||
}
|
||||
|
||||
_calculateRoomBadges(room) {
|
||||
if (!room) return;
|
||||
|
||||
const rooms = this.state.rooms.slice();
|
||||
const roomModel = rooms.find((r) => r.room.roomId === room.roomId);
|
||||
if (!roomModel) return; // No applicable room, so don't do math on it
|
||||
|
||||
const badges = this._calculateBadgesForRoom(room);
|
||||
if (!badges) return; // No badges for some reason
|
||||
|
||||
Object.assign(roomModel, badges);
|
||||
this.setState({rooms});
|
||||
}
|
||||
|
||||
_appendRoomId(roomId) {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
let room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) return;
|
||||
|
||||
const rooms = this.state.rooms.slice();
|
||||
|
||||
// If the room is upgraded, use that room instead. We'll also splice out
|
||||
// any children of the room.
|
||||
const history = MatrixClientPeg.get().getRoomUpgradeHistory(roomId);
|
||||
if (history.length > 1) {
|
||||
room = history[history.length - 1]; // Last room is most recent
|
||||
|
||||
// Take out any room that isn't the most recent room
|
||||
for (let i = 0; i < history.length - 1; i++) {
|
||||
const idx = rooms.findIndex((r) => r.room.roomId === history[i].roomId);
|
||||
if (idx !== -1) rooms.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const existingIdx = rooms.findIndex((r) => r.room.roomId === room.roomId);
|
||||
if (existingIdx !== -1) {
|
||||
rooms.splice(existingIdx, 1);
|
||||
}
|
||||
|
||||
rooms.splice(0, 0, {room, animated: false});
|
||||
|
||||
if (rooms.length > MAX_ROOMS) {
|
||||
rooms.splice(MAX_ROOMS, rooms.length - MAX_ROOMS);
|
||||
}
|
||||
this.setState({rooms});
|
||||
|
||||
if (this.refs.scroller) {
|
||||
this.refs.scroller.moveToOrigin();
|
||||
}
|
||||
}
|
||||
|
||||
_viewRoom(room, index) {
|
||||
|
@ -197,6 +269,11 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
this.setState({rooms});
|
||||
}
|
||||
|
||||
_isDmRoom(room) {
|
||||
const dmRooms = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||
return Boolean(dmRooms);
|
||||
}
|
||||
|
||||
render() {
|
||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||
const IndicatorScrollbar = sdk.getComponent('structures.IndicatorScrollbar');
|
||||
|
@ -234,11 +311,23 @@ export default class RoomBreadcrumbs extends React.Component {
|
|||
badge = <div className={badgeClasses}>{r.formattedCount}</div>;
|
||||
}
|
||||
|
||||
let dmIndicator;
|
||||
if (this._isDmRoom(r.room)) {
|
||||
dmIndicator = <img
|
||||
src={require("../../../../res/img/icon_person.svg")}
|
||||
className="mx_RoomBreadcrumbs_dmIndicator"
|
||||
width="13"
|
||||
height="15"
|
||||
alt={_t("Direct Chat")}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleButton className={classes} key={r.room.roomId} onClick={() => this._viewRoom(r.room, i)}
|
||||
onMouseEnter={() => this._onMouseEnter(r.room)} onMouseLeave={() => this._onMouseLeave(r.room)}>
|
||||
<RoomAvatar room={r.room} width={32} height={32} />
|
||||
{badge}
|
||||
{dmIndicator}
|
||||
{tooltip}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
|
|
@ -761,6 +761,7 @@
|
|||
"Seen by %(userName)s at %(dateTime)s": "Seen by %(userName)s at %(dateTime)s",
|
||||
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
|
||||
"Replying": "Replying",
|
||||
"Direct Chat": "Direct Chat",
|
||||
"No rooms to show": "No rooms to show",
|
||||
"Unnamed room": "Unnamed room",
|
||||
"World readable": "World readable",
|
||||
|
@ -1264,7 +1265,6 @@
|
|||
"Forget": "Forget",
|
||||
"Favourite": "Favourite",
|
||||
"Low Priority": "Low Priority",
|
||||
"Direct Chat": "Direct Chat",
|
||||
"Clear status": "Clear status",
|
||||
"Update status": "Update status",
|
||||
"Set status": "Set status",
|
||||
|
|
|
@ -258,6 +258,10 @@ export const SETTINGS = {
|
|||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
default: "en",
|
||||
},
|
||||
"breadcrumb_rooms": {
|
||||
supportedLevels: ['account'],
|
||||
default: [],
|
||||
},
|
||||
"analyticsOptIn": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||
displayName: _td('Send analytics data'),
|
||||
|
|
|
@ -19,6 +19,8 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
|||
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
|
||||
import {SettingLevel} from "../SettingsStore";
|
||||
|
||||
const BREADCRUMBS_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
|
||||
|
||||
/**
|
||||
* Gets and sets settings at the "account" level for the current user.
|
||||
* This handler does not make use of the roomId parameter.
|
||||
|
@ -55,6 +57,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
const val = event.getContent()[settingName];
|
||||
this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
|
||||
}
|
||||
} else if (event.getType() === BREADCRUMBS_EVENT_TYPE) {
|
||||
const val = event.getContent()['rooms'] || [];
|
||||
this._watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,6 +73,12 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
return !content['disable'];
|
||||
}
|
||||
|
||||
// Special case for breadcrumbs
|
||||
if (settingName === "breadcrumb_rooms") {
|
||||
const content = this._getSettings(BREADCRUMBS_EVENT_TYPE) || {};
|
||||
return content['rooms'] || [];
|
||||
}
|
||||
|
||||
const settings = this._getSettings() || {};
|
||||
let preferredValue = settings[settingName];
|
||||
|
||||
|
@ -89,6 +100,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
|
|||
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
|
||||
}
|
||||
|
||||
// Special case for breadcrumbs
|
||||
if (settingName === "breadcrumb_rooms") {
|
||||
const content = this._getSettings(BREADCRUMBS_EVENT_TYPE) || {};
|
||||
content['rooms'] = newValue;
|
||||
return MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content);
|
||||
}
|
||||
|
||||
const content = this._getSettings() || {};
|
||||
content[settingName] = newValue;
|
||||
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
|
||||
|
|
Loading…
Reference in a new issue