mirror of https://github.com/vector-im/riot-web
Merge remote-tracking branch 'origin/develop' into develop
commit
aff1fcde90
|
@ -78,8 +78,6 @@
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
"react-beautiful-dnd": "^4.0.0",
|
"react-beautiful-dnd": "^4.0.0",
|
||||||
"react-dnd": "^2.1.4",
|
|
||||||
"react-dnd-html5-backend": "^2.1.2",
|
|
||||||
"react-dom": "^15.4.0",
|
"react-dom": "^15.4.0",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
|
||||||
"sanitize-html": "^1.14.1",
|
"sanitize-html": "^1.14.1",
|
||||||
|
|
|
@ -19,8 +19,6 @@ limitations under the License.
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { DragDropContext } from 'react-dnd';
|
|
||||||
import HTML5Backend from 'react-dnd-html5-backend';
|
|
||||||
|
|
||||||
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
|
@ -347,4 +345,4 @@ const LoggedInView = React.createClass({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default DragDropContext(HTML5Backend)(LoggedInView);
|
export default LoggedInView;
|
||||||
|
|
|
@ -1066,10 +1066,10 @@ export default React.createClass({
|
||||||
// this if we are not scrolled up in the view. To find out, delegate to
|
// this if we are not scrolled up in the view. To find out, delegate to
|
||||||
// the timeline panel. If the timeline panel doesn't exist, then we assume
|
// the timeline panel. If the timeline panel doesn't exist, then we assume
|
||||||
// it is safe to reset the timeline.
|
// it is safe to reset the timeline.
|
||||||
if (!self._loggedInView) {
|
if (!self._loggedInView || !self._loggedInView.child) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return self._loggedInView.getDecoratedComponentInstance().canResetTimelineInRoom(roomId);
|
return self._loggedInView.child.canResetTimelineInRoom(roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on('sync', function(state, prevState) {
|
cli.on('sync', function(state, prevState) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
|
import { DragDropContext } from 'react-beautiful-dnd';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
|
@ -32,6 +33,8 @@ const Receipt = require('../../../utils/Receipt');
|
||||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||||
|
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
const HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
function phraseForSection(section) {
|
function phraseForSection(section) {
|
||||||
|
@ -275,6 +278,103 @@ module.exports = React.createClass({
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onRoomTileEndDrag: function(result) {
|
||||||
|
if (!result.destination) return;
|
||||||
|
|
||||||
|
let newTag = result.destination.droppableId.split('_')[1];
|
||||||
|
let prevTag = result.source.droppableId.split('_')[1];
|
||||||
|
if (newTag === 'undefined') newTag = undefined;
|
||||||
|
if (prevTag === 'undefined') prevTag = undefined;
|
||||||
|
|
||||||
|
const roomId = result.draggableId.split('_')[1];
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
|
||||||
|
const newIndex = result.destination.index;
|
||||||
|
|
||||||
|
// Evil hack to get DMs behaving
|
||||||
|
if ((prevTag === undefined && newTag === 'im.vector.fake.direct') ||
|
||||||
|
(prevTag === 'im.vector.fake.direct' && newTag === undefined)
|
||||||
|
) {
|
||||||
|
Rooms.guessAndSetDMRoom(
|
||||||
|
room, newTag === 'im.vector.fake.direct',
|
||||||
|
).catch((err) => {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to set direct chat tag " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to set direct chat tag'),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChangedSubLists = result.source.droppableId !== result.destination.droppableId;
|
||||||
|
|
||||||
|
let newOrder = null;
|
||||||
|
|
||||||
|
// Is the tag ordered manually?
|
||||||
|
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
|
const newList = Object.assign({}, this.state.lists[newTag]);
|
||||||
|
|
||||||
|
// If the room was moved "down" (increasing index) in the same list we
|
||||||
|
// need to use the orders of the tiles with indices shifted by +1
|
||||||
|
const offset = (
|
||||||
|
newTag === prevTag && result.source.index < result.destination.index
|
||||||
|
) ? 1 : 0;
|
||||||
|
|
||||||
|
const prevOrder = newIndex === 0 ?
|
||||||
|
0 : newList[offset + newIndex - 1].tags[newTag].order;
|
||||||
|
const nextOrder = newIndex === newList.length ?
|
||||||
|
1 : newList[offset + newIndex].tags[newTag].order;
|
||||||
|
|
||||||
|
newOrder = {
|
||||||
|
order: (prevOrder + nextOrder) / 2.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// More evilness: We will still be dealing with moving to favourites/low prio,
|
||||||
|
// but we avoid ever doing a request with 'im.vector.fake.direct`.
|
||||||
|
//
|
||||||
|
// if we moved lists, remove the old tag
|
||||||
|
if (prevTag && prevTag !== 'im.vector.fake.direct' &&
|
||||||
|
hasChangedSubLists
|
||||||
|
) {
|
||||||
|
// Optimistic update of what will happen to the room tags
|
||||||
|
delete room.tags[prevTag];
|
||||||
|
|
||||||
|
MatrixClientPeg.get().deleteRoomTag(roomId, prevTag).catch(function(err) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to remove tag " + prevTag + " from room: " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to remove tag %(tagName)s from room', {tagName: prevTag}),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we moved lists or the ordering changed, add the new tag
|
||||||
|
if (newTag && newTag !== 'im.vector.fake.direct' &&
|
||||||
|
(hasChangedSubLists || newOrder)
|
||||||
|
) {
|
||||||
|
// Optimistic update of what will happen to the room tags
|
||||||
|
room.tags[newTag] = newOrder;
|
||||||
|
|
||||||
|
MatrixClientPeg.get().setRoomTag(roomId, newTag, newOrder).catch(function(err) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to add tag " + newTag + " to room: " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to add tag %(tagName)s to room', {tagName: newTag}),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh to display the optimistic updates - this needs to be done in the
|
||||||
|
// same tick as the drag finishing otherwise the room will pop back to its
|
||||||
|
// previous position - hence no delayed refresh
|
||||||
|
this.refreshRoomList();
|
||||||
|
},
|
||||||
|
|
||||||
_delayedRefreshRoomList: new rate_limited_func(function() {
|
_delayedRefreshRoomList: new rate_limited_func(function() {
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
}, 500),
|
}, 500),
|
||||||
|
@ -649,114 +749,116 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return (
|
return (
|
||||||
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
<DragDropContext onDragEnd={this.onRoomTileEndDrag}>
|
||||||
autoshow={true} onScroll={self._whenScrolling} ref="gemscroll">
|
<GeminiScrollbar className="mx_RoomList_scrollbar"
|
||||||
<div className="mx_RoomList">
|
autoshow={true} onScroll={self._whenScrolling} ref="gemscroll">
|
||||||
<RoomSubList list={[]}
|
<div className="mx_RoomList">
|
||||||
extraTiles={this._makeGroupInviteTiles()}
|
<RoomSubList list={[]}
|
||||||
label={_t('Community Invites')}
|
extraTiles={this._makeGroupInviteTiles()}
|
||||||
editable={false}
|
label={_t('Community Invites')}
|
||||||
order="recent"
|
editable={false}
|
||||||
isInvite={true}
|
order="recent"
|
||||||
collapsed={self.props.collapsed}
|
isInvite={true}
|
||||||
searchFilter={self.props.searchFilter}
|
collapsed={self.props.collapsed}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
searchFilter={self.props.searchFilter}
|
||||||
onShowMoreRooms={self.onShowMoreRooms}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
/>
|
onShowMoreRooms={self.onShowMoreRooms}
|
||||||
|
/>
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['im.vector.fake.invite']}
|
<RoomSubList list={self.state.lists['im.vector.fake.invite']}
|
||||||
label={_t('Invites')}
|
label={_t('Invites')}
|
||||||
editable={false}
|
editable={false}
|
||||||
order="recent"
|
order="recent"
|
||||||
isInvite={true}
|
isInvite={true}
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms}
|
onShowMoreRooms={self.onShowMoreRooms}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['m.favourite']}
|
<RoomSubList list={self.state.lists['m.favourite']}
|
||||||
label={_t('Favourites')}
|
label={_t('Favourites')}
|
||||||
tagName="m.favourite"
|
tagName="m.favourite"
|
||||||
emptyContent={this._getEmptyContent('m.favourite')}
|
emptyContent={this._getEmptyContent('m.favourite')}
|
||||||
editable={true}
|
editable={true}
|
||||||
order="manual"
|
order="manual"
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />
|
onShowMoreRooms={self.onShowMoreRooms} />
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['im.vector.fake.direct']}
|
<RoomSubList list={self.state.lists['im.vector.fake.direct']}
|
||||||
label={_t('People')}
|
label={_t('People')}
|
||||||
tagName="im.vector.fake.direct"
|
tagName="im.vector.fake.direct"
|
||||||
emptyContent={this._getEmptyContent('im.vector.fake.direct')}
|
emptyContent={this._getEmptyContent('im.vector.fake.direct')}
|
||||||
headerItems={this._getHeaderItems('im.vector.fake.direct')}
|
headerItems={this._getHeaderItems('im.vector.fake.direct')}
|
||||||
editable={true}
|
editable={true}
|
||||||
order="recent"
|
order="recent"
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
alwaysShowHeader={true}
|
alwaysShowHeader={true}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />
|
onShowMoreRooms={self.onShowMoreRooms} />
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['im.vector.fake.recent']}
|
<RoomSubList list={self.state.lists['im.vector.fake.recent']}
|
||||||
label={_t('Rooms')}
|
label={_t('Rooms')}
|
||||||
editable={true}
|
editable={true}
|
||||||
emptyContent={this._getEmptyContent('im.vector.fake.recent')}
|
emptyContent={this._getEmptyContent('im.vector.fake.recent')}
|
||||||
headerItems={this._getHeaderItems('im.vector.fake.recent')}
|
headerItems={this._getHeaderItems('im.vector.fake.recent')}
|
||||||
order="recent"
|
order="recent"
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />
|
onShowMoreRooms={self.onShowMoreRooms} />
|
||||||
|
|
||||||
{ Object.keys(self.state.lists).map((tagName) => {
|
{ Object.keys(self.state.lists).map((tagName) => {
|
||||||
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
return <RoomSubList list={self.state.lists[tagName]}
|
return <RoomSubList list={self.state.lists[tagName]}
|
||||||
key={tagName}
|
key={tagName}
|
||||||
label={tagName}
|
label={tagName}
|
||||||
tagName={tagName}
|
tagName={tagName}
|
||||||
emptyContent={this._getEmptyContent(tagName)}
|
emptyContent={this._getEmptyContent(tagName)}
|
||||||
editable={true}
|
editable={true}
|
||||||
order="manual"
|
order="manual"
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />;
|
onShowMoreRooms={self.onShowMoreRooms} />;
|
||||||
}
|
}
|
||||||
}) }
|
}) }
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['m.lowpriority']}
|
<RoomSubList list={self.state.lists['m.lowpriority']}
|
||||||
label={_t('Low priority')}
|
label={_t('Low priority')}
|
||||||
tagName="m.lowpriority"
|
tagName="m.lowpriority"
|
||||||
emptyContent={this._getEmptyContent('m.lowpriority')}
|
emptyContent={this._getEmptyContent('m.lowpriority')}
|
||||||
editable={true}
|
editable={true}
|
||||||
order="recent"
|
order="recent"
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onHeaderClick={self.onSubListHeaderClick}
|
onHeaderClick={self.onSubListHeaderClick}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />
|
onShowMoreRooms={self.onShowMoreRooms} />
|
||||||
|
|
||||||
<RoomSubList list={self.state.lists['im.vector.fake.archived']}
|
<RoomSubList list={self.state.lists['im.vector.fake.archived']}
|
||||||
label={_t('Historical')}
|
label={_t('Historical')}
|
||||||
editable={false}
|
editable={false}
|
||||||
order="recent"
|
order="recent"
|
||||||
collapsed={self.props.collapsed}
|
collapsed={self.props.collapsed}
|
||||||
alwaysShowHeader={true}
|
alwaysShowHeader={true}
|
||||||
startAsHidden={true}
|
startAsHidden={true}
|
||||||
showSpinner={self.state.isLoadingLeftRooms}
|
showSpinner={self.state.isLoadingLeftRooms}
|
||||||
onHeaderClick= {self.onArchivedHeaderClick}
|
onHeaderClick= {self.onArchivedHeaderClick}
|
||||||
incomingCall={self.state.incomingCall}
|
incomingCall={self.state.incomingCall}
|
||||||
searchFilter={self.props.searchFilter}
|
searchFilter={self.props.searchFilter}
|
||||||
onShowMoreRooms={self.onShowMoreRooms} />
|
onShowMoreRooms={self.onShowMoreRooms} />
|
||||||
</div>
|
</div>
|
||||||
</GeminiScrollbar>
|
</GeminiScrollbar>
|
||||||
|
</DragDropContext>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,10 +35,7 @@ module.exports = React.createClass({
|
||||||
displayName: 'RoomTile',
|
displayName: 'RoomTile',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
connectDragSource: PropTypes.func,
|
|
||||||
connectDropTarget: PropTypes.func,
|
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
isDragging: PropTypes.bool,
|
|
||||||
|
|
||||||
room: PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
collapsed: PropTypes.bool.isRequired,
|
collapsed: PropTypes.bool.isRequired,
|
||||||
|
@ -256,35 +253,19 @@ module.exports = React.createClass({
|
||||||
directMessageIndicator = <img src="img/icon_person.svg" className="mx_RoomTile_dm" width="11" height="13" alt="dm" />;
|
directMessageIndicator = <img src="img/icon_person.svg" className="mx_RoomTile_dm" width="11" height="13" alt="dm" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These props are injected by React DnD,
|
return <AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||||
// as defined by your `collect` function above:
|
<div className={avatarClasses}>
|
||||||
const isDragging = this.props.isDragging;
|
<div className="mx_RoomTile_avatar_container">
|
||||||
const connectDragSource = this.props.connectDragSource;
|
<RoomAvatar room={this.props.room} width={24} height={24} />
|
||||||
const connectDropTarget = this.props.connectDropTarget;
|
{ directMessageIndicator }
|
||||||
|
|
||||||
|
|
||||||
let ret = (
|
|
||||||
<div> { /* Only native elements can be wrapped in a DnD object. */ }
|
|
||||||
<AccessibleButton className={classes} tabIndex="0" onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
|
||||||
<div className={avatarClasses}>
|
|
||||||
<div className="mx_RoomTile_avatar_container">
|
|
||||||
<RoomAvatar room={this.props.room} width={24} height={24} />
|
|
||||||
{ directMessageIndicator }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomTile_nameContainer">
|
|
||||||
{ label }
|
|
||||||
{ badge }
|
|
||||||
</div>
|
|
||||||
{ /* { incomingCallBox } */ }
|
|
||||||
{ tooltip }
|
|
||||||
</AccessibleButton>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="mx_RoomTile_nameContainer">
|
||||||
|
{ label }
|
||||||
if (connectDropTarget) ret = connectDropTarget(ret);
|
{ badge }
|
||||||
if (connectDragSource) ret = connectDragSource(ret);
|
</div>
|
||||||
|
{ /* { incomingCallBox } */ }
|
||||||
return ret;
|
{ tooltip }
|
||||||
|
</AccessibleButton>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue