Merge branch 'develop' into travis/hide-images

This commit is contained in:
Travis Ralston 2019-10-01 09:31:54 -06:00
commit 3c589cffec
69 changed files with 1103 additions and 290 deletions

View file

@ -36,7 +36,7 @@ import classnames from 'classnames';
import GroupStore from '../../stores/GroupStore';
import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../matrix-to";
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
const LONG_DESC_PLACEHOLDER = _td(

View file

@ -22,7 +22,7 @@ import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { DragDropContext } from 'react-beautiful-dnd';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import { Key, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
import PageTypes from '../../PageTypes';
import CallMediaHandler from '../../CallMediaHandler';
import { fixupColorFonts } from '../../utils/FontManager';
@ -353,23 +353,23 @@ const LoggedInView = createReactClass({
const hasModifier = ev.altKey || ev.ctrlKey || ev.metaKey || ev.shiftKey ||
ev.key === "Alt" || ev.key === "Control" || ev.key === "Meta" || ev.key === "Shift";
switch (ev.keyCode) {
case KeyCode.PAGE_UP:
case KeyCode.PAGE_DOWN:
switch (ev.key) {
case Key.PAGE_UP:
case Key.PAGE_DOWN:
if (!hasModifier) {
this._onScrollKeyPressed(ev);
handled = true;
}
break;
case KeyCode.HOME:
case KeyCode.END:
case Key.HOME:
case Key.END:
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
this._onScrollKeyPressed(ev);
handled = true;
}
break;
case KeyCode.KEY_K:
case Key.K:
if (ctrlCmdOnly) {
dis.dispatch({
action: 'focus_room_filter',
@ -377,7 +377,9 @@ const LoggedInView = createReactClass({
handled = true;
}
break;
case KeyCode.KEY_BACKTICK:
case Key.BACKTICK:
if (ev.key !== "`") break;
// Ideally this would be CTRL+P for "Profile", but that's
// taken by the print dialog. CTRL+I for "Information"
// was previously chosen but conflicted with italics in

View file

@ -307,11 +307,11 @@ const RoomSubList = createReactClass({
});
if (isCollapsed) {
return <div ref="subList" className={subListClasses}>
return <div ref="subList" className={subListClasses} role="group" aria-label={this.props.label}>
{this._getHeaderJsx(isCollapsed)}
</div>;
} else if (this._canUseLazyListRendering()) {
return <div ref="subList" className={subListClasses}>
return <div ref="subList" className={subListClasses} role="group" aria-label={this.props.label}>
{this._getHeaderJsx(isCollapsed)}
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll" onScroll={ this._onScroll }>
<LazyRenderList
@ -325,7 +325,7 @@ const RoomSubList = createReactClass({
} else {
const roomTiles = this.props.list.map(r => this.makeRoomTile(r));
const tiles = roomTiles.concat(this.props.extraTiles);
return <div ref="subList" className={subListClasses}>
return <div ref="subList" className={subListClasses} role="group" aria-label={this.props.label}>
{this._getHeaderJsx(isCollapsed)}
<IndicatorScrollbar ref="scroller" className="mx_RoomSubList_scroll" onScroll={ this._onScroll }>
{ tiles }
@ -340,7 +340,7 @@ const RoomSubList = createReactClass({
}
return (
<div ref="subList" className="mx_RoomSubList">
<div ref="subList" className="mx_RoomSubList" role="group" aria-label={this.props.label}>
{ this._getHeaderJsx(isCollapsed) }
{ content }
</div>

View file

@ -31,7 +31,7 @@ import Promise from 'bluebird';
import classNames from 'classnames';
import {Room} from "matrix-js-sdk";
import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from '../../matrix-to';
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
import MatrixClientPeg from '../../MatrixClientPeg';
import ContentMessages from '../../ContentMessages';

View file

@ -52,7 +52,7 @@ module.exports = createReactClass({
// Extra node inserted after picker input, dropdown and errors
extraNode: PropTypes.node,
value: PropTypes.string,
placeholder: PropTypes.oneOfType(PropTypes.string, PropTypes.func),
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
roomId: PropTypes.string,
button: PropTypes.string,
focus: PropTypes.bool,

View file

@ -20,7 +20,7 @@ import {Room, User, Group, RoomMember, MatrixEvent} from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from 'qrcode-react';
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../matrix-to";
import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import * as ContextualMenu from "../../structures/ContextualMenu";
const socials = [

View file

@ -90,21 +90,17 @@ export default class TermsDialog extends React.PureComponent {
}
}
_summaryForServiceType(serviceType, docName) {
_summaryForServiceType(serviceType) {
switch (serviceType) {
case Matrix.SERVICE_TYPES.IS:
return <div>
{_t("Find others by phone or email")}
<br />
{_t("Be found by phone or email")}
{docName !== null ? <br /> : ''}
{docName !== null ? '('+docName+')' : ''}
</div>;
case Matrix.SERVICE_TYPES.IM:
return <div>
{_t("Use bots, bridges, widgets and sticker packs")}
{docName !== null ? <br /> : ''}
{docName !== null ? '('+docName+')' : ''}
</div>;
}
}
@ -128,19 +124,19 @@ export default class TermsDialog extends React.PureComponent {
const termDoc = policyValues[i];
const termsLang = pickBestLanguage(Object.keys(termDoc).filter((k) => k !== 'version'));
let serviceName;
let summary;
if (i === 0) {
serviceName = this._nameForServiceType(policiesAndService.service.serviceType, parsedBaseUrl.host);
summary = this._summaryForServiceType(
policiesAndService.service.serviceType,
);
}
const summary = this._summaryForServiceType(
policiesAndService.service.serviceType,
policyValues.length > 1 ? termDoc[termsLang].name : null,
);
rows.push(<tr key={termDoc[termsLang].url}>
<td className="mx_TermsDialog_service">{serviceName}</td>
<td className="mx_TermsDialog_summary">{summary}</td>
<td><a rel="noopener" target="_blank" href={termDoc[termsLang].url}>
<div className="mx_TermsDialog_link" />
<td>{termDoc[termsLang].name} <a rel="noopener" target="_blank" href={termDoc[termsLang].url}>
<span className="mx_TermsDialog_link" />
</a></td>
<td><TermsCheckbox
url={termDoc[termsLang].url}
@ -184,13 +180,13 @@ export default class TermsDialog extends React.PureComponent {
hasCancel={false}
>
<div id='mx_Dialog_content'>
<p>{_t("To continue you need to accept the Terms of this service.")}</p>
<p>{_t("To continue you need to accept the terms of this service.")}</p>
<table className="mx_TermsDialog_termsTable"><tbody>
<tr className="mx_TermsDialog_termsTableHeader">
<th>{_t("Service")}</th>
<th>{_t("Summary")}</th>
<th>{_t("Terms")}</th>
<th>{_t("Document")}</th>
<th>{_t("Accept")}</th>
</tr>
{rows}

View file

@ -68,7 +68,7 @@ export default function AccessibleButton(props) {
delete restProps.inputRef;
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.role = restProps.role || "button";
restProps.className = (restProps.className ? restProps.className + " " : "") + "mx_AccessibleButton";
if (kind) {

View file

@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
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.
@ -22,23 +23,21 @@ import classNames from 'classnames';
import { Room, RoomMember, MatrixClient } from 'matrix-js-sdk';
import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg';
import { MATRIXTO_URL_PATTERN } from '../../../linkify-matrix';
import { getDisplayAliasForRoom } from '../../../Rooms';
import FlairStore from "../../../stores/FlairStore";
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
// For URLs of matrix.to links in the timeline which have been reformatted by
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_MATRIXTO = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const Pill = createReactClass({
statics: {
isPillUrl: (url) => {
return !!REGEX_MATRIXTO.exec(url);
return !!getPrimaryPermalinkEntity(url);
},
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_MATRIXTO.exec(url);
return !!REGEX_LOCAL_PERMALINK.exec(url);
},
roomNotifPos: (text) => {
return text.indexOf("@room");
@ -95,22 +94,21 @@ const Pill = createReactClass({
},
async componentWillReceiveProps(nextProps) {
let regex = REGEX_MATRIXTO;
if (nextProps.inMessage) {
regex = REGEX_LOCAL_MATRIXTO;
}
let matrixToMatch;
let resourceId;
let prefix;
if (nextProps.url) {
// Default to the empty array if no match for simplicity
// resource and prefix will be undefined instead of throwing
matrixToMatch = regex.exec(nextProps.url) || [];
if (nextProps.inMessage) {
// Default to the empty array if no match for simplicity
// resource and prefix will be undefined instead of throwing
const matrixToMatch = REGEX_LOCAL_PERMALINK.exec(nextProps.url) || [];
resourceId = matrixToMatch[1]; // The room/user ID
prefix = matrixToMatch[2]; // The first character of prefix
resourceId = matrixToMatch[1]; // The room/user ID
prefix = matrixToMatch[2]; // The first character of prefix
} else {
resourceId = getPrimaryPermalinkEntity(nextProps.url);
prefix = resourceId ? resourceId[0] : undefined;
}
}
const pillType = this.props.type || {

View file

@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import {wantsDateSeparator} from '../../../DateUtils';
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
import {makeUserPermalink, RoomPermalinkCreator} from "../../../matrix-to";
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
// This component does no cycle detection, simply because the only way to make such a cycle would be to

View file

@ -59,7 +59,9 @@ export default createReactClass({
);
const av = (
<BaseAvatar name={this.props.member.userId}
<BaseAvatar
name={this.props.member.displayname || this.props.member.userId}
idName={this.props.member.userId}
width={36} height={36}
url={avatarUrl}
/>

View file

@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher';
import { RoomPermalinkCreator } from '../../../matrix-to';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';

View file

@ -30,9 +30,9 @@ import { _t } from '../../../languageHandler';
import * as ContextualMenu from '../../structures/ContextualMenu';
import SettingsStore from "../../../settings/SettingsStore";
import ReplyThread from "../elements/ReplyThread";
import {host as matrixtoHost} from '../../../matrix-to';
import {pillifyLinks} from '../../../utils/pillify';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import {isPermalinkHost} from "../../../utils/permalinks/Permalinks";
module.exports = createReactClass({
displayName: 'TextualBody',
@ -248,10 +248,10 @@ module.exports = createReactClass({
const url = node.getAttribute("href");
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
// never preview matrix.to links (if anything we should give a smart
// never preview permalinks (if anything we should give a smart
// preview of the room/user they point to: nobody needs to be reminded
// what the matrix.to site looks like).
if (host === matrixtoHost) return false;
if (isPermalinkHost(host)) return false;
if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link

View file

@ -20,18 +20,17 @@ import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
import isEqual from 'lodash/isEqual';
import sdk from '../../../index';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Promise from 'bluebird';
import { Room } from 'matrix-js-sdk';
import {getCompletions} from '../../../autocomplete/Autocompleter';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
const COMPOSER_SELECTED = 0;
export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`;
export default class Autocomplete extends React.Component {
constructor(props) {
super(props);
@ -224,7 +223,7 @@ export default class Autocomplete extends React.Component {
setSelection(selectionOffset: number) {
this.setState({selectionOffset, hide: false});
if (this.props.onSelectionChange) {
this.props.onSelectionChange(this.state.completionList[selectionOffset - 1]);
this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1);
}
}
@ -250,9 +249,8 @@ export default class Autocomplete extends React.Component {
let position = 1;
const renderedCompletions = this.state.completions.map((completionResult, i) => {
const completions = completionResult.completions.map((completion, i) => {
const className = classNames('mx_Autocomplete_Completion', {
'selected': position === this.state.selectionOffset,
});
const selected = position === this.state.selectionOffset;
const className = classNames('mx_Autocomplete_Completion', {selected});
const componentPosition = position;
position++;
@ -261,10 +259,12 @@ export default class Autocomplete extends React.Component {
};
return React.cloneElement(completion.component, {
key: i,
ref: `completion${position - 1}`,
"key": i,
"ref": `completion${componentPosition}`,
"id": generateCompletionDomId(componentPosition - 1), // 0 index the completion IDs
className,
onClick,
"aria-selected": selected,
});
});

View file

@ -28,7 +28,7 @@ import {
replaceRangeAndMoveCaret,
} from '../../../editor/operations';
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
import Autocomplete from '../rooms/Autocomplete';
import Autocomplete, {generateCompletionDomId} from '../rooms/Autocomplete';
import {autoCompleteCreator} from '../../../editor/parts';
import {parsePlainTextMessage} from '../../../editor/deserialize';
import {renderModel} from '../../../editor/render';
@ -432,8 +432,9 @@ export default class BasicMessageEditor extends React.Component {
this.props.model.autoComplete.onComponentConfirm(completion);
}
_onAutoCompleteSelectionChange = (completion) => {
_onAutoCompleteSelectionChange = (completion, completionIndex) => {
this.props.model.autoComplete.onComponentSelectionChange(completion);
this.setState({completionIndex});
}
componentWillUnmount() {
@ -535,6 +536,8 @@ export default class BasicMessageEditor extends React.Component {
quote: ctrlShortcutLabel(">"),
};
const {completionIndex} = this.state;
return (<div className={classes}>
{ autoComplete }
<MessageComposerFormatBar ref={ref => this._formatBarRef = ref} onAction={this._onFormatAction} shortcuts={shortcuts} />
@ -548,7 +551,13 @@ export default class BasicMessageEditor extends React.Component {
onKeyDown={this._onKeyDown}
ref={ref => this._editorRef = ref}
aria-label={this.props.label}
></div>
role="textbox"
aria-multiline="true"
aria-autocomplete="both"
aria-haspopup="listbox"
aria-expanded={Boolean(this.state.autoComplete)}
aria-activedescendant={completionIndex >= 0 ? generateCompletionDomId(completionIndex) : undefined}
/>
</div>);
}

View file

@ -23,7 +23,7 @@ import sdk from '../../../index';
import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../matrix-to';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';
import E2EIcon from './E2EIcon';

View file

@ -48,13 +48,11 @@ import Markdown from '../../../Markdown';
import MessageComposerStore from '../../../stores/MessageComposerStore';
import ContentMessages from '../../../ContentMessages';
import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';
import EMOJIBASE from 'emojibase-data/en/compact.json';
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import {makeUserPermalink} from "../../../matrix-to";
import {getPrimaryPermalinkEntity, makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread";
@ -224,18 +222,15 @@ export default class MessageComposerInput extends React.Component {
// special case links
if (tag === 'a') {
const href = el.getAttribute('href');
let m;
if (href) {
m = href.match(MATRIXTO_URL_PATTERN);
}
if (m) {
const permalinkEntity = getPrimaryPermalinkEntity(href);
if (permalinkEntity) {
return {
object: 'inline',
type: 'pill',
data: {
href,
completion: el.innerText,
completionId: m[1],
completionId: permalinkEntity,
},
};
} else {
@ -541,7 +536,7 @@ export default class MessageComposerInput extends React.Component {
const textWithMdPills = this.plainWithMdPills.serialize(editorState);
const markdown = new Markdown(textWithMdPills);
// HTML deserialize has custom rules to turn matrix.to links into pill objects.
// HTML deserialize has custom rules to turn permalinks into pill objects.
return this.html.deserialize(markdown.toHTML());
}

View file

@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import PropTypes from "prop-types";
import {RoomPermalinkCreator} from "../../../matrix-to";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
function cancelQuoting() {
dis.dispatch({

View file

@ -805,7 +805,7 @@ module.exports = createReactClass({
const subListComponents = this._mapSubListProps(subLists);
return (
<div ref={this._collectResizeContainer} className="mx_RoomList"
<div ref={this._collectResizeContainer} className="mx_RoomList" role="listbox" aria-label={_t("Rooms")}
onMouseMove={this.onMouseMove} onMouseLeave={this.onMouseLeave}>
{ subListComponents }
</div>

View file

@ -33,6 +33,7 @@ import AccessibleButton from '../elements/AccessibleButton';
import ActiveRoomObserver from '../../../ActiveRoomObserver';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore from "../../../settings/SettingsStore";
import {_t} from "../../../languageHandler";
module.exports = createReactClass({
displayName: 'RoomTile',
@ -368,6 +369,8 @@ module.exports = createReactClass({
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
let ariaLabel = name;
let dmIndicator;
if (this._isDirectMessageRoom(this.props.room.roomId)) {
dmIndicator = <img
@ -379,12 +382,24 @@ module.exports = createReactClass({
/>;
}
if (notifBadges && mentionBadges && !isInvite) {
ariaLabel += " " + _t("It has %(count)s unread messages including mentions.", {
count: notificationCount,
});
} else if (notifBadges) {
ariaLabel += " " + _t("It has %(count)s unread messages.", { count: notificationCount });
} else if (mentionBadges && !isInvite) {
ariaLabel += " " + _t("It has unread mentions.");
}
return <AccessibleButton tabIndex="0"
className={classes}
onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onContextMenu={this.onContextMenu}
aria-label={ariaLabel}
role="option"
>
<div className={avatarClasses}>
<div className="mx_RoomTile_avatar_container">

View file

@ -182,7 +182,7 @@ export default class SendMessageComposer extends React.Component {
// be extra resilient when somehow the AutocompleteWrapperModel or
// CommandPartCreator fails to insert a command part, so we don't send
// a command as a message
if (firstPart.type === "plain" && firstPart.text.startsWith("/")) {
if (firstPart.text.startsWith("/") && (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
return true;
}
}

View file

@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../matrix-to';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import {_t, pickBestLanguage} from "../../../languageHandler";
import sdk from "../../../..";
import sdk from "../../..";
export default class InlineTermsAgreement extends React.Component {
static propTypes = {