Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into rxl881/appFixes
This commit is contained in:
commit
185379b037
10 changed files with 178 additions and 71 deletions
|
@ -49,6 +49,12 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
||||||
|
|
||||||
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
// (see https://github.com/vector-im/riot-web/issues/4762)
|
||||||
|
if (/^(\/join|\/leave)/.test(query)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
let completions = [];
|
let completions = [];
|
||||||
const {command, range} = this.getCurrentCommand(query, selection, force);
|
const {command, range} = this.getCurrentCommand(query, selection, force);
|
||||||
|
|
|
@ -48,13 +48,21 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
|
||||||
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
||||||
|
|
||||||
|
// Disable autocompletions when composing commands because of various issues
|
||||||
|
// (see https://github.com/vector-im/riot-web/issues/4762)
|
||||||
|
if (/^(\/ban|\/unban|\/op|\/deop|\/invite|\/kick|\/verify)/.test(query)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let completions = [];
|
let completions = [];
|
||||||
let {command, range} = this.getCurrentCommand(query, selection, force);
|
let {command, range} = this.getCurrentCommand(query, selection, force);
|
||||||
if (command) {
|
if (command) {
|
||||||
completions = this.matcher.match(command[0]).map((user) => {
|
completions = this.matcher.match(command[0]).map((user) => {
|
||||||
const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
|
const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
|
||||||
return {
|
return {
|
||||||
completion: displayName,
|
// Length of completion should equal length of text in decorator. draft-js
|
||||||
|
// relies on the length of the entity === length of the text in the decoration.
|
||||||
|
completion: user.rawDisplayName.replace(' (IRC)', ''),
|
||||||
suffix: range.start === 0 ? ': ' : ' ',
|
suffix: range.start === 0 ? ': ' : ' ',
|
||||||
href: 'https://matrix.to/#/' + user.userId,
|
href: 'https://matrix.to/#/' + user.userId,
|
||||||
component: (
|
component: (
|
||||||
|
|
|
@ -131,9 +131,6 @@ module.exports = React.createClass({
|
||||||
// the master view we are showing.
|
// the master view we are showing.
|
||||||
view: VIEWS.LOADING,
|
view: VIEWS.LOADING,
|
||||||
|
|
||||||
// a thing to call showScreen with once login completes.
|
|
||||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
|
||||||
|
|
||||||
// What the LoggedInView would be showing if visible
|
// What the LoggedInView would be showing if visible
|
||||||
page_type: null,
|
page_type: null,
|
||||||
|
|
||||||
|
@ -147,8 +144,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
collapse_lhs: false,
|
collapse_lhs: false,
|
||||||
collapse_rhs: false,
|
collapse_rhs: false,
|
||||||
ready: false,
|
|
||||||
width: 10000,
|
|
||||||
leftOpacity: 1.0,
|
leftOpacity: 1.0,
|
||||||
middleOpacity: 1.0,
|
middleOpacity: 1.0,
|
||||||
rightOpacity: 1.0,
|
rightOpacity: 1.0,
|
||||||
|
@ -274,6 +269,15 @@ module.exports = React.createClass({
|
||||||
register_hs_url: paramHs,
|
register_hs_url: paramHs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a thing to call showScreen with once login completes. this is kept
|
||||||
|
// outside this.state because updating it should never trigger a
|
||||||
|
// rerender.
|
||||||
|
this._screenAfterLogin = this.props.initialScreenAfterLogin;
|
||||||
|
|
||||||
|
this._windowWidth = 10000;
|
||||||
|
this.handleResize();
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -294,9 +298,6 @@ module.exports = React.createClass({
|
||||||
linkifyMatrix.onGroupClick = this.onGroupClick;
|
linkifyMatrix.onGroupClick = this.onGroupClick;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
this.handleResize();
|
|
||||||
|
|
||||||
const teamServerConfig = this.props.config.teamServerConfig || {};
|
const teamServerConfig = this.props.config.teamServerConfig || {};
|
||||||
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
|
Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
|
||||||
|
|
||||||
|
@ -312,13 +313,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// if the user has followed a login or register link, don't reanimate
|
// if the user has followed a login or register link, don't reanimate
|
||||||
// the old creds, but rather go straight to the relevant page
|
// the old creds, but rather go straight to the relevant page
|
||||||
const firstScreen = this.state.screenAfterLogin ?
|
const firstScreen = this._screenAfterLogin ?
|
||||||
this.state.screenAfterLogin.screen : null;
|
this._screenAfterLogin.screen : null;
|
||||||
|
|
||||||
if (firstScreen === 'login' ||
|
if (firstScreen === 'login' ||
|
||||||
firstScreen === 'register' ||
|
firstScreen === 'register' ||
|
||||||
firstScreen === 'forgot_password') {
|
firstScreen === 'forgot_password') {
|
||||||
this.setState({loading: false});
|
|
||||||
this._showScreenAfterLogin();
|
this._showScreenAfterLogin();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -367,9 +367,9 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
const newState = {
|
const newState = {
|
||||||
viewUserId: null,
|
viewUserId: null,
|
||||||
};
|
};
|
||||||
Object.assign(newState, state);
|
Object.assign(newState, state);
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
|
@ -992,14 +992,12 @@ module.exports = React.createClass({
|
||||||
_showScreenAfterLogin: function() {
|
_showScreenAfterLogin: function() {
|
||||||
// If screenAfterLogin is set, use that, then null it so that a second login will
|
// If screenAfterLogin is set, use that, then null it so that a second login will
|
||||||
// result in view_home_page, _user_settings or _room_directory
|
// result in view_home_page, _user_settings or _room_directory
|
||||||
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
if (this._screenAfterLogin && this._screenAfterLogin.screen) {
|
||||||
this.showScreen(
|
this.showScreen(
|
||||||
this.state.screenAfterLogin.screen,
|
this._screenAfterLogin.screen,
|
||||||
this.state.screenAfterLogin.params,
|
this._screenAfterLogin.params,
|
||||||
);
|
);
|
||||||
// XXX: is this necessary? `showScreen` should do it for us.
|
this._screenAfterLogin = null;
|
||||||
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
|
||||||
this.setState({screenAfterLogin: null});
|
|
||||||
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
|
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
|
||||||
// Before defaulting to directory, show the last viewed room
|
// Before defaulting to directory, show the last viewed room
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -1276,20 +1274,20 @@ module.exports = React.createClass({
|
||||||
const hideRhsThreshold = 820;
|
const hideRhsThreshold = 820;
|
||||||
const showRhsThreshold = 820;
|
const showRhsThreshold = 820;
|
||||||
|
|
||||||
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
if (this._windowWidth > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||||
dis.dispatch({ action: 'hide_left_panel' });
|
dis.dispatch({ action: 'hide_left_panel' });
|
||||||
}
|
}
|
||||||
if (this.state.width <= showLhsThreshold && window.innerWidth > showLhsThreshold) {
|
if (this._windowWidth <= showLhsThreshold && window.innerWidth > showLhsThreshold) {
|
||||||
dis.dispatch({ action: 'show_left_panel' });
|
dis.dispatch({ action: 'show_left_panel' });
|
||||||
}
|
}
|
||||||
if (this.state.width > hideRhsThreshold && window.innerWidth <= hideRhsThreshold) {
|
if (this._windowWidth > hideRhsThreshold && window.innerWidth <= hideRhsThreshold) {
|
||||||
dis.dispatch({ action: 'hide_right_panel' });
|
dis.dispatch({ action: 'hide_right_panel' });
|
||||||
}
|
}
|
||||||
if (this.state.width <= showRhsThreshold && window.innerWidth > showRhsThreshold) {
|
if (this._windowWidth <= showRhsThreshold && window.innerWidth > showRhsThreshold) {
|
||||||
dis.dispatch({ action: 'show_right_panel' });
|
dis.dispatch({ action: 'show_right_panel' });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({width: window.innerWidth});
|
this._windowWidth = window.innerWidth;
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomCreated: function(roomId) {
|
onRoomCreated: function(roomId) {
|
||||||
|
|
|
@ -101,6 +101,10 @@ const SETTINGS_LABELS = [
|
||||||
id: 'MessageComposerInput.autoReplaceEmoji',
|
id: 'MessageComposerInput.autoReplaceEmoji',
|
||||||
label: 'Automatically replace plain text Emoji',
|
label: 'Automatically replace plain text Emoji',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'Pill.shouldHidePillAvatar',
|
||||||
|
label: 'Hide avatars in user and room mentions',
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
id: 'useFixedWidthFont',
|
id: 'useFixedWidthFont',
|
||||||
|
|
|
@ -47,6 +47,8 @@ const Pill = React.createClass({
|
||||||
inMessage: PropTypes.bool,
|
inMessage: PropTypes.bool,
|
||||||
// The room in which this pill is being rendered
|
// The room in which this pill is being rendered
|
||||||
room: PropTypes.instanceOf(Room),
|
room: PropTypes.instanceOf(Room),
|
||||||
|
// Whether to include an avatar in the pill
|
||||||
|
shouldShowPillAvatar: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
|
@ -155,7 +157,9 @@ const Pill = React.createClass({
|
||||||
if (member) {
|
if (member) {
|
||||||
userId = member.userId;
|
userId = member.userId;
|
||||||
linkText = member.rawDisplayName.replace(' (IRC)', ''); // FIXME when groups are done
|
linkText = member.rawDisplayName.replace(' (IRC)', ''); // FIXME when groups are done
|
||||||
avatar = <MemberAvatar member={member} width={16} height={16}/>;
|
if (this.props.shouldShowPillAvatar) {
|
||||||
|
avatar = <MemberAvatar member={member} width={16} height={16}/>;
|
||||||
|
}
|
||||||
pillClass = 'mx_UserPill';
|
pillClass = 'mx_UserPill';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +168,9 @@ const Pill = React.createClass({
|
||||||
const room = this.state.room;
|
const room = this.state.room;
|
||||||
if (room) {
|
if (room) {
|
||||||
linkText = (room ? getDisplayAliasForRoom(room) : null) || resource;
|
linkText = (room ? getDisplayAliasForRoom(room) : null) || resource;
|
||||||
avatar = <RoomAvatar room={room} width={16} height={16}/>;
|
if (this.props.shouldShowPillAvatar) {
|
||||||
|
avatar = <RoomAvatar room={room} width={16} height={16}/>;
|
||||||
|
}
|
||||||
pillClass = 'mx_RoomPill';
|
pillClass = 'mx_RoomPill';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
pillifyLinks: function(nodes) {
|
pillifyLinks: function(nodes) {
|
||||||
|
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||||
for (let i = 0; i < nodes.length; i++) {
|
for (let i = 0; i < nodes.length; i++) {
|
||||||
const node = nodes[i];
|
const node = nodes[i];
|
||||||
if (node.tagName === "A" && node.getAttribute("href")) {
|
if (node.tagName === "A" && node.getAttribute("href")) {
|
||||||
|
@ -181,7 +182,12 @@ module.exports = React.createClass({
|
||||||
const pillContainer = document.createElement('span');
|
const pillContainer = document.createElement('span');
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
const pill = <Pill url={href} inMessage={true} room={room}/>;
|
const pill = <Pill
|
||||||
|
url={href}
|
||||||
|
inMessage={true}
|
||||||
|
room={room}
|
||||||
|
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||||
|
/>;
|
||||||
|
|
||||||
ReactDOM.render(pill, pillContainer);
|
ReactDOM.render(pill, pillContainer);
|
||||||
node.parentNode.replaceChild(pillContainer, node);
|
node.parentNode.replaceChild(pillContainer, node);
|
||||||
|
|
|
@ -172,7 +172,7 @@ export default class Autocomplete extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.setState({hide: true, selectionOffset: 0});
|
this.setState({hide: true, selectionOffset: 0, completions: [], completionList: []});
|
||||||
}
|
}
|
||||||
|
|
||||||
forceComplete() {
|
forceComplete() {
|
||||||
|
|
|
@ -155,7 +155,9 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
componentWillReceiveProps: function(nextProps) {
|
||||||
if (nextProps.mxEvent !== this.props.mxEvent) {
|
// re-check the sender verification as outgoing events progress through
|
||||||
|
// the send process.
|
||||||
|
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
|
||||||
this._verifyEvent(nextProps.mxEvent);
|
this._verifyEvent(nextProps.mxEvent);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -386,6 +388,36 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderE2EPadlock: function() {
|
||||||
|
const ev = this.props.mxEvent;
|
||||||
|
const props = {onClick: this.onCryptoClicked};
|
||||||
|
|
||||||
|
|
||||||
|
if (ev.getContent().msgtype === 'm.bad.encrypted') {
|
||||||
|
return <E2ePadlockUndecryptable {...props}/>;
|
||||||
|
} else if (ev.isEncrypted()) {
|
||||||
|
if (this.state.verified) {
|
||||||
|
return <E2ePadlockVerified {...props}/>;
|
||||||
|
} else {
|
||||||
|
return <E2ePadlockUnverified {...props}/>;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// XXX: if the event is being encrypted (ie eventSendStatus ===
|
||||||
|
// encrypting), it might be nice to show something other than the
|
||||||
|
// open padlock?
|
||||||
|
|
||||||
|
// if the event is not encrypted, but it's an e2e room, show the
|
||||||
|
// open padlock
|
||||||
|
const e2eEnabled = this.props.matrixClient.isRoomEncrypted(ev.getRoomId());
|
||||||
|
if (e2eEnabled) {
|
||||||
|
return <E2ePadlockUnencrypted {...props}/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no padlock needed
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
|
||||||
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
var SenderProfile = sdk.getComponent('messages.SenderProfile');
|
||||||
|
@ -407,7 +439,6 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
throw new Error("Event type not supported");
|
throw new Error("Event type not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
var e2eEnabled = this.props.matrixClient.isRoomEncrypted(this.props.mxEvent.getRoomId());
|
|
||||||
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
|
||||||
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
|
const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted;
|
||||||
|
|
||||||
|
@ -485,26 +516,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const editButton = (
|
const editButton = (
|
||||||
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
|
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
|
||||||
);
|
);
|
||||||
let e2e;
|
|
||||||
// cosmetic padlocks:
|
|
||||||
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
|
||||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12" />;
|
|
||||||
}
|
|
||||||
// real padlocks
|
|
||||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
|
||||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Undecryptable")} src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
|
||||||
}
|
|
||||||
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12"/>;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by an unverified device")} src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e2eEnabled) {
|
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Unencrypted message")} src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
|
||||||
}
|
|
||||||
const timestamp = this.props.mxEvent.getTs() ?
|
const timestamp = this.props.mxEvent.getTs() ?
|
||||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
||||||
|
@ -572,7 +584,7 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
<a href={ permalink } onClick={this.onPermalinkClicked}>
|
<a href={ permalink } onClick={this.onPermalinkClicked}>
|
||||||
{ timestamp }
|
{ timestamp }
|
||||||
</a>
|
</a>
|
||||||
{ e2e }
|
{ this._renderE2EPadlock() }
|
||||||
<EventTileType ref="tile"
|
<EventTileType ref="tile"
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
highlights={this.props.highlights}
|
highlights={this.props.highlights}
|
||||||
|
@ -597,3 +609,39 @@ module.exports.haveTileForEvent = function(e) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function E2ePadlockUndecryptable(props) {
|
||||||
|
return (
|
||||||
|
<E2ePadlock alt={_t("Undecryptable")}
|
||||||
|
src="img/e2e-blocked.svg" width="12" height="12"
|
||||||
|
style={{ marginLeft: "-1px" }} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function E2ePadlockVerified(props) {
|
||||||
|
return (
|
||||||
|
<E2ePadlock alt={_t("Encrypted by a verified device")}
|
||||||
|
src="img/e2e-verified.svg" width="10" height="12"
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function E2ePadlockUnverified(props) {
|
||||||
|
return (
|
||||||
|
<E2ePadlock alt={_t("Encrypted by an unverified device")}
|
||||||
|
src="img/e2e-warning.svg" width="15" height="12"
|
||||||
|
style={{ marginLeft: "-2px" }} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function E2ePadlockUnencrypted(props) {
|
||||||
|
return (
|
||||||
|
<E2ePadlock alt={_t("Unencrypted message")}
|
||||||
|
src="img/e2e-unencrypted.svg" width="12" height="12"
|
||||||
|
{...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function E2ePadlock(props) {
|
||||||
|
return <img className="mx_EventTile_e2eIcon" {...props} />;
|
||||||
|
}
|
||||||
|
|
|
@ -97,20 +97,39 @@ export default class MessageComposerInput extends React.Component {
|
||||||
onInputStateChanged: React.PropTypes.func,
|
onInputStateChanged: React.PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static getKeyBinding(e: SyntheticKeyboardEvent): string {
|
static getKeyBinding(ev: SyntheticKeyboardEvent): string {
|
||||||
// C-m => Toggles between rich text and markdown modes
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||||
if (e.keyCode === KeyCode.KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
|
let ctrlCmdOnly;
|
||||||
return 'toggle-mode';
|
if (isMac) {
|
||||||
|
ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||||
|
} else {
|
||||||
|
ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow opening of dev tools. getDefaultKeyBinding would be 'italic' for KEY_I
|
// Restrict a subset of key bindings to ONLY having ctrl/meta* pressed and
|
||||||
if (e.keyCode === KeyCode.KEY_I && e.shiftKey && e.ctrlKey) {
|
// importantly NOT having alt, shift, meta/ctrl* pressed. draft-js does not
|
||||||
// When null is returned, draft-js will NOT preventDefault, allowing dev tools
|
// handle this in `getDefaultKeyBinding` so we do it ourselves here.
|
||||||
// to be toggled when the editor is focussed
|
//
|
||||||
return null;
|
// * if macOS, read second option
|
||||||
|
const ctrlCmdCommand = {
|
||||||
|
// C-m => Toggles between rich text and markdown modes
|
||||||
|
[KeyCode.KEY_M]: 'toggle-mode',
|
||||||
|
[KeyCode.KEY_B]: 'bold',
|
||||||
|
[KeyCode.KEY_I]: 'italic',
|
||||||
|
[KeyCode.KEY_U]: 'underline',
|
||||||
|
[KeyCode.KEY_J]: 'code',
|
||||||
|
[KeyCode.KEY_O]: 'split-block',
|
||||||
|
}[ev.keyCode];
|
||||||
|
|
||||||
|
if (ctrlCmdCommand) {
|
||||||
|
if (!ctrlCmdOnly) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ctrlCmdCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDefaultKeyBinding(e);
|
// Handle keys such as return, left and right arrows etc.
|
||||||
|
return getDefaultKeyBinding(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getBlockStyle(block: ContentBlock): ?string {
|
static getBlockStyle(block: ContentBlock): ?string {
|
||||||
|
@ -185,13 +204,19 @@ export default class MessageComposerInput extends React.Component {
|
||||||
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
|
createEditorState(richText: boolean, contentState: ?ContentState): EditorState {
|
||||||
const decorators = richText ? RichText.getScopedRTDecorators(this.props) :
|
const decorators = richText ? RichText.getScopedRTDecorators(this.props) :
|
||||||
RichText.getScopedMDDecorators(this.props);
|
RichText.getScopedMDDecorators(this.props);
|
||||||
|
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
|
||||||
decorators.push({
|
decorators.push({
|
||||||
strategy: this.findLinkEntities.bind(this),
|
strategy: this.findLinkEntities.bind(this),
|
||||||
component: (entityProps) => {
|
component: (entityProps) => {
|
||||||
const Pill = sdk.getComponent('elements.Pill');
|
const Pill = sdk.getComponent('elements.Pill');
|
||||||
const {url} = entityProps.contentState.getEntity(entityProps.entityKey).getData();
|
const {url} = entityProps.contentState.getEntity(entityProps.entityKey).getData();
|
||||||
if (Pill.isPillUrl(url)) {
|
if (Pill.isPillUrl(url)) {
|
||||||
return <Pill url={url} room={this.props.room} offsetKey={entityProps.offsetKey}/>;
|
return <Pill
|
||||||
|
url={url}
|
||||||
|
room={this.props.room}
|
||||||
|
offsetKey={entityProps.offsetKey}
|
||||||
|
shouldShowPillAvatar={shouldShowPillAvatar}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -244,7 +269,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
// paths for inserting a user pill is not fun
|
// paths for inserting a user pill is not fun
|
||||||
const selection = this.state.editorState.getSelection();
|
const selection = this.state.editorState.getSelection();
|
||||||
const member = this.props.room.getMember(payload.user_id);
|
const member = this.props.room.getMember(payload.user_id);
|
||||||
const completion = member ? member.name.replace(' (IRC)', '') : payload.user_id;
|
const completion = member ?
|
||||||
|
member.rawDisplayName.replace(' (IRC)', '') : payload.user_id;
|
||||||
this.setDisplayedCompletion({
|
this.setDisplayedCompletion({
|
||||||
completion,
|
completion,
|
||||||
selection,
|
selection,
|
||||||
|
@ -254,10 +280,13 @@ export default class MessageComposerInput extends React.Component {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'quote': {
|
case 'quote': {
|
||||||
let {body, formatted_body} = payload.event.getContent();
|
let {body} = payload.event.getContent();
|
||||||
formatted_body = formatted_body || escape(body);
|
/// XXX: Not doing rich-text quoting from formatted-body because draft-js
|
||||||
if (formatted_body) {
|
/// has regressed such that when links are quoted, errors are thrown. See
|
||||||
let content = RichText.htmlToContentState(`<blockquote>${formatted_body}</blockquote>`);
|
/// https://github.com/vector-im/riot-web/issues/4756.
|
||||||
|
body = escape(body);
|
||||||
|
if (body) {
|
||||||
|
let content = RichText.htmlToContentState(`<blockquote>${body}</blockquote>`);
|
||||||
if (!this.state.isRichtextEnabled) {
|
if (!this.state.isRichtextEnabled) {
|
||||||
content = ContentState.createFromText(RichText.stateToMarkdown(content));
|
content = ContentState.createFromText(RichText.stateToMarkdown(content));
|
||||||
}
|
}
|
||||||
|
@ -516,7 +545,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
newState = RichUtils.toggleInlineStyle(this.state.editorState, 'STRIKETHROUGH');
|
newState = RichUtils.toggleInlineStyle(this.state.editorState, 'STRIKETHROUGH');
|
||||||
} else if (shouldToggleBlockFormat) {
|
} else if (shouldToggleBlockFormat) {
|
||||||
const currentStartOffset = this.state.editorState.getSelection().getStartOffset();
|
const currentStartOffset = this.state.editorState.getSelection().getStartOffset();
|
||||||
if (currentStartOffset === 0) {
|
const currentEndOffset = this.state.editorState.getSelection().getEndOffset();
|
||||||
|
if (currentStartOffset === 0 && currentEndOffset === 0) {
|
||||||
// Toggle current block type (setting it to 'unstyled')
|
// Toggle current block type (setting it to 'unstyled')
|
||||||
newState = RichUtils.toggleBlockType(this.state.editorState, currentBlockType);
|
newState = RichUtils.toggleBlockType(this.state.editorState, currentBlockType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -966,5 +966,6 @@
|
||||||
"Edit Group": "Edit Group",
|
"Edit Group": "Edit Group",
|
||||||
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
|
||||||
"Failed to upload image": "Failed to upload image",
|
"Failed to upload image": "Failed to upload image",
|
||||||
"Failed to update group": "Failed to update group"
|
"Failed to update group": "Failed to update group",
|
||||||
|
"Hide avatars in user and room mentions": "Hide avatars in user and room mentions"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue