[CONFLICT CHUNKS] Merge branch 'develop' into travis/sourcemaps-develop
This commit is contained in:
commit
fde32f13a5
190 changed files with 6185 additions and 2225 deletions
|
@ -17,7 +17,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { KeyCode } from '../../../Keyboard';
|
||||
import {Key} from '../../../Keyboard';
|
||||
|
||||
/**
|
||||
* AccessibleButton is a generic wrapper for any element that should be treated
|
||||
|
@ -40,23 +40,23 @@ export default function AccessibleButton(props) {
|
|||
// Browsers handle space and enter keypresses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
restProps.onKeyDown = function(e) {
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
if (e.key === Key.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
if (e.key === Key.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
restProps.onKeyUp = function(e) {
|
||||
if (e.keyCode === KeyCode.SPACE) {
|
||||
if (e.key === Key.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.keyCode === KeyCode.ENTER) {
|
||||
if (e.key === Key.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
|
|||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
|
||||
const {title, ...props} = this.props;
|
||||
const {title, children, ...props} = this.props;
|
||||
|
||||
const tip = this.state.hover ? <Tooltip
|
||||
className="mx_AccessibleTooltipButton_container"
|
||||
|
@ -57,6 +57,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
|
|||
/> : <div />;
|
||||
return (
|
||||
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
|
||||
{ children }
|
||||
{ tip }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,8 @@ import * as sdk from '../../../index';
|
|||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
|
||||
// XXX: This component is *not* cross-signing aware. Once everything is
|
||||
// cross-signing, this component should just go away.
|
||||
export default createReactClass({
|
||||
displayName: 'DeviceVerifyButtons',
|
||||
|
||||
|
@ -59,7 +61,7 @@ export default createReactClass({
|
|||
Modal.createTrackedDialog('Device Verify Dialog', '', DeviceVerifyDialog, {
|
||||
userId: this.props.userId,
|
||||
device: this.state.device,
|
||||
});
|
||||
}, null, /* priority = */ false, /* static = */ true);
|
||||
},
|
||||
|
||||
onUnverifyClick: function() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,11 +16,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {createRef} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import AccessibleButton from './AccessibleButton';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {Key} from "../../../Keyboard";
|
||||
|
||||
class MenuOption extends React.Component {
|
||||
constructor(props) {
|
||||
|
@ -48,9 +50,14 @@ class MenuOption extends React.Component {
|
|||
mx_Dropdown_option_highlight: this.props.highlighted,
|
||||
});
|
||||
|
||||
return <div className={optClasses}
|
||||
return <div
|
||||
id={this.props.id}
|
||||
className={optClasses}
|
||||
onClick={this._onClick}
|
||||
onMouseEnter={this._onMouseEnter}
|
||||
role="option"
|
||||
aria-selected={this.props.highlighted}
|
||||
ref={this.props.inputRef}
|
||||
>
|
||||
{ this.props.children }
|
||||
</div>;
|
||||
|
@ -66,6 +73,7 @@ MenuOption.propTypes = {
|
|||
dropdownKey: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onMouseEnter: PropTypes.func.isRequired,
|
||||
inputRef: PropTypes.any,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -86,8 +94,6 @@ export default class Dropdown extends React.Component {
|
|||
this._onRootClick = this._onRootClick.bind(this);
|
||||
this._onDocumentClick = this._onDocumentClick.bind(this);
|
||||
this._onMenuOptionClick = this._onMenuOptionClick.bind(this);
|
||||
this._onInputKeyPress = this._onInputKeyPress.bind(this);
|
||||
this._onInputKeyUp = this._onInputKeyUp.bind(this);
|
||||
this._onInputChange = this._onInputChange.bind(this);
|
||||
this._collectRoot = this._collectRoot.bind(this);
|
||||
this._collectInputTextBox = this._collectInputTextBox.bind(this);
|
||||
|
@ -111,6 +117,7 @@ export default class Dropdown extends React.Component {
|
|||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._button = createRef();
|
||||
// Listen for all clicks on the document so we can close the
|
||||
// menu when the user clicks somewhere else
|
||||
document.addEventListener('click', this._onDocumentClick, false);
|
||||
|
@ -169,40 +176,49 @@ export default class Dropdown extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_onMenuOptionClick(dropdownKey) {
|
||||
_close() {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
});
|
||||
this.props.onOptionChange(dropdownKey);
|
||||
}
|
||||
|
||||
_onInputKeyPress(e) {
|
||||
// This needs to be on the keypress event because otherwise
|
||||
// it can't cancel the form submission
|
||||
if (e.key == 'Enter') {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
});
|
||||
this.props.onOptionChange(this.state.highlightedOption);
|
||||
e.preventDefault();
|
||||
// their focus was on the input, its getting unmounted, move it to the button
|
||||
if (this._button.current) {
|
||||
this._button.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
_onInputKeyUp(e) {
|
||||
// These keys don't generate keypress events and so needs to
|
||||
// be on keyup
|
||||
if (e.key == 'Escape') {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
});
|
||||
} else if (e.key == 'ArrowDown') {
|
||||
this.setState({
|
||||
highlightedOption: this._nextOption(this.state.highlightedOption),
|
||||
});
|
||||
} else if (e.key == 'ArrowUp') {
|
||||
this.setState({
|
||||
highlightedOption: this._prevOption(this.state.highlightedOption),
|
||||
});
|
||||
_onMenuOptionClick(dropdownKey) {
|
||||
this._close();
|
||||
this.props.onOptionChange(dropdownKey);
|
||||
}
|
||||
|
||||
_onInputKeyDown = (e) => {
|
||||
let handled = true;
|
||||
|
||||
// These keys don't generate keypress events and so needs to be on keyup
|
||||
switch (e.key) {
|
||||
case Key.ENTER:
|
||||
this.props.onOptionChange(this.state.highlightedOption);
|
||||
// fallthrough
|
||||
case Key.ESCAPE:
|
||||
this._close();
|
||||
break;
|
||||
case Key.ARROW_DOWN:
|
||||
this.setState({
|
||||
highlightedOption: this._nextOption(this.state.highlightedOption),
|
||||
});
|
||||
break;
|
||||
case Key.ARROW_UP:
|
||||
this.setState({
|
||||
highlightedOption: this._prevOption(this.state.highlightedOption),
|
||||
});
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,20 +266,34 @@ export default class Dropdown extends React.Component {
|
|||
return keys[(index - 1) % keys.length];
|
||||
}
|
||||
|
||||
_scrollIntoView(node) {
|
||||
if (node) {
|
||||
node.scrollIntoView({
|
||||
block: "nearest",
|
||||
behavior: "auto",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getMenuOptions() {
|
||||
const options = React.Children.map(this.props.children, (child) => {
|
||||
const highlighted = this.state.highlightedOption === child.key;
|
||||
return (
|
||||
<MenuOption key={child.key} dropdownKey={child.key}
|
||||
highlighted={this.state.highlightedOption == child.key}
|
||||
<MenuOption
|
||||
id={`${this.props.id}__${child.key}`}
|
||||
key={child.key}
|
||||
dropdownKey={child.key}
|
||||
highlighted={highlighted}
|
||||
onMouseEnter={this._setHighlightedOption}
|
||||
onClick={this._onMenuOptionClick}
|
||||
inputRef={highlighted ? this._scrollIntoView : undefined}
|
||||
>
|
||||
{ child }
|
||||
</MenuOption>
|
||||
);
|
||||
});
|
||||
if (options.length === 0) {
|
||||
return [<div key="0" className="mx_Dropdown_option">
|
||||
return [<div key="0" className="mx_Dropdown_option" role="option">
|
||||
{ _t("No results") }
|
||||
</div>];
|
||||
}
|
||||
|
@ -279,23 +309,35 @@ export default class Dropdown extends React.Component {
|
|||
let menu;
|
||||
if (this.state.expanded) {
|
||||
if (this.props.searchEnabled) {
|
||||
currentValue = <input type="text" className="mx_Dropdown_option"
|
||||
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
|
||||
onKeyUp={this._onInputKeyUp}
|
||||
onChange={this._onInputChange}
|
||||
value={this.state.searchQuery}
|
||||
/>;
|
||||
currentValue = (
|
||||
<input
|
||||
type="text"
|
||||
className="mx_Dropdown_option"
|
||||
ref={this._collectInputTextBox}
|
||||
onKeyDown={this._onInputKeyDown}
|
||||
onChange={this._onInputChange}
|
||||
value={this.state.searchQuery}
|
||||
role="combobox"
|
||||
aria-autocomplete="list"
|
||||
aria-activedescendant={`${this.props.id}__${this.state.highlightedOption}`}
|
||||
aria-owns={`${this.props.id}_listbox`}
|
||||
aria-disabled={this.props.disabled}
|
||||
aria-label={this.props.label}
|
||||
/>
|
||||
);
|
||||
}
|
||||
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
|
||||
{ this._getMenuOptions() }
|
||||
</div>;
|
||||
menu = (
|
||||
<div className="mx_Dropdown_menu" style={menuStyle} role="listbox" id={`${this.props.id}_listbox`}>
|
||||
{ this._getMenuOptions() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentValue) {
|
||||
const selectedChild = this.props.getShortOption ?
|
||||
this.props.getShortOption(this.props.value) :
|
||||
this.childrenByKey[this.props.value];
|
||||
currentValue = <div className="mx_Dropdown_option">
|
||||
currentValue = <div className="mx_Dropdown_option" id={`${this.props.id}_value`}>
|
||||
{ selectedChild }
|
||||
</div>;
|
||||
}
|
||||
|
@ -311,9 +353,18 @@ export default class Dropdown extends React.Component {
|
|||
// Note the menu sits inside the AccessibleButton div so it's anchored
|
||||
// to the input, but overflows below it. The root contains both.
|
||||
return <div className={classnames(dropdownClasses)} ref={this._collectRoot}>
|
||||
<AccessibleButton className="mx_Dropdown_input mx_no_textinput" onClick={this._onInputClick}>
|
||||
<AccessibleButton
|
||||
className="mx_Dropdown_input mx_no_textinput"
|
||||
onClick={this._onInputClick}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={this.state.expanded}
|
||||
disabled={this.props.disabled}
|
||||
inputRef={this._button}
|
||||
aria-label={this.props.label}
|
||||
aria-describedby={`${this.props.id}_value`}
|
||||
>
|
||||
{ currentValue }
|
||||
<span className="mx_Dropdown_arrow"></span>
|
||||
<span className="mx_Dropdown_arrow" />
|
||||
{ menu }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
|
@ -321,6 +372,7 @@ export default class Dropdown extends React.Component {
|
|||
}
|
||||
|
||||
Dropdown.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
// The width that the dropdown should be. If specified,
|
||||
// the dropped-down part of the menu will be set to this
|
||||
// width.
|
||||
|
@ -340,4 +392,6 @@ Dropdown.propTypes = {
|
|||
value: PropTypes.string,
|
||||
// negative for consistency with HTML
|
||||
disabled: PropTypes.bool,
|
||||
// ARIA label
|
||||
label: PropTypes.string.isRequired,
|
||||
};
|
||||
|
|
|
@ -25,13 +25,13 @@ import * as sdk from '../../../index';
|
|||
* Parent components should supply an 'onSubmit' callback which returns a
|
||||
* promise; a spinner is shown until the promise resolves.
|
||||
*
|
||||
* The parent can also supply a 'getIntialValue' callback, which works in a
|
||||
* The parent can also supply a 'getInitialValue' callback, which works in a
|
||||
* similarly asynchronous way. If this is not provided, the initial value is
|
||||
* taken from the 'initialValue' property.
|
||||
*/
|
||||
export default class EditableTextContainer extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._unmounted = false;
|
||||
this.state = {
|
||||
|
|
|
@ -66,10 +66,14 @@ export default class Field extends React.PureComponent {
|
|||
this.state = {
|
||||
valid: undefined,
|
||||
feedback: undefined,
|
||||
focused: false,
|
||||
};
|
||||
}
|
||||
|
||||
onFocus = (ev) => {
|
||||
this.setState({
|
||||
focused: true,
|
||||
});
|
||||
this.validate({
|
||||
focused: true,
|
||||
});
|
||||
|
@ -88,6 +92,9 @@ export default class Field extends React.PureComponent {
|
|||
};
|
||||
|
||||
onBlur = (ev) => {
|
||||
this.setState({
|
||||
focused: false,
|
||||
});
|
||||
this.validate({
|
||||
focused: false,
|
||||
});
|
||||
|
@ -112,7 +119,9 @@ export default class Field extends React.PureComponent {
|
|||
allowEmpty,
|
||||
});
|
||||
|
||||
if (feedback) {
|
||||
// this method is async and so we may have been blurred since the method was called
|
||||
// if we have then hide the feedback as withValidation does
|
||||
if (this.state.focused && feedback) {
|
||||
this.setState({
|
||||
valid,
|
||||
feedback,
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClient} from 'matrix-js-sdk';
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import dis from '../../../dispatcher';
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
|
||||
class FlairAvatar extends React.Component {
|
||||
|
@ -40,7 +40,7 @@ class FlairAvatar extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const httpUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||
const httpUrl = this.context.mxcUrlToHttp(
|
||||
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
|
||||
const tooltip = this.props.groupProfile.name ?
|
||||
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
||||
|
@ -62,9 +62,7 @@ FlairAvatar.propTypes = {
|
|||
}),
|
||||
};
|
||||
|
||||
FlairAvatar.contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
FlairAvatar.contextType = MatrixClientContext;
|
||||
|
||||
export default class Flair extends React.Component {
|
||||
constructor() {
|
||||
|
@ -92,7 +90,7 @@ export default class Flair extends React.Component {
|
|||
for (const groupId of groups) {
|
||||
let groupProfile = null;
|
||||
try {
|
||||
groupProfile = await FlairStore.getGroupProfileCached(this.context.matrixClient, groupId);
|
||||
groupProfile = await FlairStore.getGroupProfileCached(this.context, groupId);
|
||||
} catch (err) {
|
||||
console.error('Could not get profile for group', groupId, err);
|
||||
}
|
||||
|
@ -134,6 +132,4 @@ Flair.propTypes = {
|
|||
groups: PropTypes.arrayOf(PropTypes.string),
|
||||
};
|
||||
|
||||
Flair.contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
Flair.contextType = MatrixClientContext;
|
||||
|
|
|
@ -22,10 +22,14 @@ import PropTypes from 'prop-types';
|
|||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import {formatDate} from '../../../DateUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
<<<<<<< HEAD
|
||||
import filesize from "filesize";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import Modal from "../../../Modal";
|
||||
import * as sdk from "../../../index";
|
||||
=======
|
||||
import {Key} from "../../../Keyboard";
|
||||
>>>>>>> develop
|
||||
|
||||
export default class ImageView extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -60,7 +64,7 @@ export default class ImageView extends React.Component {
|
|||
}
|
||||
|
||||
onKeyDown = (ev) => {
|
||||
if (ev.keyCode === 27) { // escape
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.props.onFinished();
|
||||
|
|
|
@ -21,6 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import * as sdk from '../../../index';
|
||||
import * as languageHandler from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
function languageMatchesSearchQuery(query, language) {
|
||||
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||
|
@ -105,9 +106,14 @@ export default class LanguageDropdown extends React.Component {
|
|||
value = this.props.value || language;
|
||||
}
|
||||
|
||||
return <Dropdown className={this.props.className}
|
||||
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
|
||||
searchEnabled={true} value={value}
|
||||
return <Dropdown
|
||||
id="mx_LanguageDropdown"
|
||||
className={this.props.className}
|
||||
onOptionChange={this.props.onOptionChange}
|
||||
onSearchChange={this._onSearchChange}
|
||||
searchEnabled={true}
|
||||
value={value}
|
||||
label={_t("Language Dropdown")}
|
||||
>
|
||||
{ options }
|
||||
</Dropdown>;
|
||||
|
|
|
@ -56,14 +56,20 @@ class ItemRange {
|
|||
}
|
||||
|
||||
export default class LazyRenderList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
const range = LazyRenderList.getVisibleRangeFromProps(props);
|
||||
const intersectRange = range.expand(props.overflowMargin);
|
||||
const renderRange = range.expand(props.overflowItems);
|
||||
const listHasChangedSize = !!state && renderRange.totalSize() !== state.renderRange.totalSize();
|
||||
const listHasChangedSize = !!state.renderRange && renderRange.totalSize() !== state.renderRange.totalSize();
|
||||
// only update render Range if the list has shrunk/grown and we need to adjust padding OR
|
||||
// if the new range + overflowMargin isn't contained by the old anymore
|
||||
if (listHasChangedSize || !state || !state.renderRange.contains(intersectRange)) {
|
||||
if (listHasChangedSize || !state.renderRange || !state.renderRange.contains(intersectRange)) {
|
||||
return {renderRange};
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -20,12 +20,13 @@ import createReactClass from 'create-react-class';
|
|||
import * as sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
|
||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { getDisplayAliasForRoom } from '../../../Rooms';
|
||||
import FlairStore from "../../../stores/FlairStore";
|
||||
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||
|
@ -66,17 +67,6 @@ const Pill = createReactClass({
|
|||
isSelected: PropTypes.bool,
|
||||
},
|
||||
|
||||
|
||||
childContextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
matrixClient: this._matrixClient,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
// ID/alias of the room/user
|
||||
|
@ -127,7 +117,7 @@ const Pill = createReactClass({
|
|||
}
|
||||
break;
|
||||
case Pill.TYPE_USER_MENTION: {
|
||||
const localMember = nextProps.room.getMember(resourceId);
|
||||
const localMember = nextProps.room ? nextProps.room.getMember(resourceId) : undefined;
|
||||
member = localMember;
|
||||
if (!localMember) {
|
||||
member = new RoomMember(null, resourceId);
|
||||
|
@ -271,20 +261,22 @@ const Pill = createReactClass({
|
|||
}
|
||||
|
||||
const classes = classNames("mx_Pill", pillClass, {
|
||||
"mx_UserPill_me": userId === MatrixClientPeg.get().credentials.userId,
|
||||
"mx_UserPill_me": userId === MatrixClientPeg.get().getUserId(),
|
||||
"mx_UserPill_selected": this.props.isSelected,
|
||||
});
|
||||
|
||||
if (this.state.pillType) {
|
||||
return this.props.inMessage ?
|
||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</a> :
|
||||
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</span>;
|
||||
return <MatrixClientContext.Provider value={this._matrixClient}>
|
||||
{ this.props.inMessage ?
|
||||
<a className={classes} href={href} onClick={onClick} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</a> :
|
||||
<span className={classes} title={resource} data-offset-key={this.props.offsetKey}>
|
||||
{ avatar }
|
||||
{ linkText }
|
||||
</span> }
|
||||
</MatrixClientContext.Provider>;
|
||||
} else {
|
||||
// Deliberately render nothing if the URL isn't recognised
|
||||
return null;
|
||||
|
|
|
@ -21,10 +21,11 @@ import {_t} from '../../../languageHandler';
|
|||
import PropTypes from 'prop-types';
|
||||
import dis from '../../../dispatcher';
|
||||
import {wantsDateSeparator} from '../../../DateUtils';
|
||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
||||
import {MatrixEvent} from 'matrix-js-sdk';
|
||||
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import escapeHtml from "escape-html";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||
|
@ -38,12 +39,10 @@ export default class ReplyThread extends React.Component {
|
|||
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
};
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// The loaded events to be rendered as linear-replies
|
||||
|
@ -187,7 +186,7 @@ export default class ReplyThread extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
this.unmounted = false;
|
||||
this.room = this.context.matrixClient.getRoom(this.props.parentEv.getRoomId());
|
||||
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
|
||||
this.room.on("Room.redaction", this.onRoomRedaction);
|
||||
// same event handler as Room.redaction as for both we just do forceUpdate
|
||||
this.room.on("Room.redactionCancelled", this.onRoomRedaction);
|
||||
|
@ -259,7 +258,7 @@ export default class ReplyThread extends React.Component {
|
|||
try {
|
||||
// ask the client to fetch the event we want using the context API, only interface to do so is to ask
|
||||
// for a timeline with that event, but once it is loaded we can use findEventById to look up the ev map
|
||||
await this.context.matrixClient.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
||||
await this.context.getEventTimeline(this.room.getUnfilteredTimelineSet(), eventId);
|
||||
} catch (e) {
|
||||
// if it fails catch the error and return early, there's no point trying to find the event in this case.
|
||||
// Return null as it is falsey and thus should be treated as an error (as the event cannot be resolved).
|
||||
|
@ -300,7 +299,7 @@ export default class ReplyThread extends React.Component {
|
|||
} else if (this.state.loadedEv) {
|
||||
const ev = this.state.loadedEv;
|
||||
const Pill = sdk.getComponent('elements.Pill');
|
||||
const room = this.context.matrixClient.getRoom(ev.getRoomId());
|
||||
const room = this.context.getRoom(ev.getRoomId());
|
||||
header = <blockquote className="mx_ReplyThread">
|
||||
{
|
||||
_t('<a>In reply to</a> <pill>', {}, {
|
||||
|
|
|
@ -20,11 +20,13 @@ import * as sdk from '../../../index';
|
|||
import withValidation from './Validation';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
|
||||
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
|
||||
export default class RoomAliasField extends React.PureComponent {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
@ -53,6 +55,7 @@ export default class RoomAliasField extends React.PureComponent {
|
|||
onValidate={this._onValidate}
|
||||
placeholder={_t("e.g. my-room")}
|
||||
onChange={this._onChange}
|
||||
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
|
||||
maxLength={maxlength} />
|
||||
);
|
||||
}
|
||||
|
@ -61,7 +64,7 @@ export default class RoomAliasField extends React.PureComponent {
|
|||
if (this.props.onChange) {
|
||||
this.props.onChange(this._asFullAlias(ev.target.value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_onValidate = async (fieldState) => {
|
||||
const result = await this._validationRules(fieldState);
|
||||
|
|
|
@ -24,8 +24,8 @@ export default class SyntaxHighlight extends React.Component {
|
|||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._ref = this._ref.bind(this);
|
||||
}
|
||||
|
|
|
@ -20,17 +20,21 @@ import React, {createRef} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import createReactClass from 'create-react-class';
|
||||
import classNames from 'classnames';
|
||||
<<<<<<< HEAD
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import * as sdk from '../../../index';
|
||||
=======
|
||||
import sdk from '../../../index';
|
||||
>>>>>>> develop
|
||||
import dis from '../../../dispatcher';
|
||||
import {_t} from '../../../languageHandler';
|
||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||
|
||||
import FlairStore from '../../../stores/FlairStore';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
|
||||
import {ContextMenu, toRightOf} from "../../structures/ContextMenu";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
// 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:
|
||||
|
@ -46,8 +50,8 @@ export default createReactClass({
|
|||
tag: PropTypes.string,
|
||||
},
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||
statics: {
|
||||
contextType: MatrixClientContext,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
|
@ -56,6 +60,8 @@ export default createReactClass({
|
|||
hover: false,
|
||||
// The profile data of the group if this.props.tag is a group ID
|
||||
profile: null,
|
||||
// Whether or not the context menu is open
|
||||
menuDisplayed: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -81,7 +87,7 @@ export default createReactClass({
|
|||
_onFlairStoreUpdated() {
|
||||
if (this.unmounted) return;
|
||||
FlairStore.getGroupProfileCached(
|
||||
this.context.matrixClient,
|
||||
this.context,
|
||||
this.props.tag,
|
||||
).then((profile) => {
|
||||
if (this.unmounted) return;
|
||||
|
@ -112,12 +118,10 @@ export default createReactClass({
|
|||
},
|
||||
|
||||
onMouseOver: function() {
|
||||
console.log("DEBUG onMouseOver");
|
||||
this.setState({hover: true});
|
||||
},
|
||||
|
||||
onMouseOut: function() {
|
||||
console.log("DEBUG onMouseOut");
|
||||
this.setState({hover: false});
|
||||
},
|
||||
|
||||
|
@ -140,12 +144,11 @@ export default createReactClass({
|
|||
|
||||
render: function() {
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||
const profile = this.state.profile || {};
|
||||
const name = profile.name || this.props.tag;
|
||||
const avatarHeight = 40;
|
||||
|
||||
const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
|
||||
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
||||
) : null;
|
||||
|
||||
|
@ -164,9 +167,6 @@ export default createReactClass({
|
|||
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badge.count)}</div>);
|
||||
}
|
||||
|
||||
const tip = this.state.hover ?
|
||||
<Tooltip className="mx_TagTile_tooltip" label={name} /> :
|
||||
<div />;
|
||||
// FIXME: this ought to use AccessibleButton for a11y but that causes onMouseOut/onMouseOver to fire too much
|
||||
const contextButton = this.state.hover || this.state.menuDisplayed ?
|
||||
<div className="mx_TagTile_context_button" onClick={this.openMenu} ref={this._contextMenuButton}>
|
||||
|
@ -184,14 +184,9 @@ export default createReactClass({
|
|||
);
|
||||
}
|
||||
|
||||
const AccessibleTooltipButton = sdk.getComponent("elements.AccessibleTooltipButton");
|
||||
return <React.Fragment>
|
||||
<ContextMenuButton
|
||||
className={className}
|
||||
onClick={this.onClick}
|
||||
onContextMenu={this.openMenu}
|
||||
label={_t("Options")}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
>
|
||||
<AccessibleTooltipButton className={className} onClick={this.onClick} onContextMenu={this.openMenu} title={name}>
|
||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||
<BaseAvatar
|
||||
name={name}
|
||||
|
@ -200,11 +195,10 @@ export default createReactClass({
|
|||
width={avatarHeight}
|
||||
height={avatarHeight}
|
||||
/>
|
||||
{ tip }
|
||||
{ contextButton }
|
||||
{ badgeElement }
|
||||
</div>
|
||||
</ContextMenuButton>
|
||||
</AccessibleTooltipButton>
|
||||
|
||||
{ contextMenu }
|
||||
</React.Fragment>;
|
||||
|
|
|
@ -19,46 +19,32 @@ import React from "react";
|
|||
import PropTypes from "prop-types";
|
||||
import classNames from "classnames";
|
||||
|
||||
import {KeyCode} from "../../../Keyboard";
|
||||
import sdk from "../../../index";
|
||||
|
||||
// Controlled Toggle Switch element
|
||||
// Controlled Toggle Switch element, written with Accessibility in mind
|
||||
const ToggleSwitch = ({checked, disabled=false, onChange, ...props}) => {
|
||||
const _onClick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (disabled) return;
|
||||
|
||||
onChange(!checked);
|
||||
};
|
||||
|
||||
const _onKeyDown = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (disabled) return;
|
||||
|
||||
if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) {
|
||||
onChange(!checked);
|
||||
}
|
||||
};
|
||||
|
||||
const classes = classNames({
|
||||
"mx_ToggleSwitch": true,
|
||||
"mx_ToggleSwitch_on": checked,
|
||||
"mx_ToggleSwitch_enabled": !disabled,
|
||||
});
|
||||
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
return (
|
||||
<div {...props}
|
||||
<AccessibleButton {...props}
|
||||
className={classes}
|
||||
onClick={_onClick}
|
||||
onKeyDown={_onKeyDown}
|
||||
role="checkbox"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
aria-disabled={disabled}
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="mx_ToggleSwitch_ball" />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue