Merge branch 'develop' into travis/copy2

This commit is contained in:
Travis Ralston 2020-09-01 10:50:58 -06:00
commit afec470ec3
65 changed files with 355 additions and 1650 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
}, },
overrides: [{ overrides: [{
"files": ["src/**/*.{ts, tsx}"], "files": ["src/**/*.{ts,tsx}"],
"extends": ["matrix-org/ts"], "extends": ["matrix-org/ts"],
"rules": { "rules": {
// We disable this while we're transitioning // We disable this while we're transitioning

View file

@ -1,3 +1,72 @@
Changes in [3.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0) (2020-09-01)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0-rc.1...v3.3.0)
* Upgrade to JS SDK 8.2.0
Changes in [3.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0-rc.1) (2020-08-26)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0...v3.3.0-rc.1)
* Upgrade to JS SDK 8.2.0-rc.1
* Update from Weblate
[\#5146](https://github.com/matrix-org/matrix-react-sdk/pull/5146)
* BaseAvatar avoid initial render with default avatar
[\#5142](https://github.com/matrix-org/matrix-react-sdk/pull/5142)
* Enforce Secure Backup completion when requested by HS
[\#5130](https://github.com/matrix-org/matrix-react-sdk/pull/5130)
* Communities v2 prototype: Explore rooms, global state, and default room
[\#5139](https://github.com/matrix-org/matrix-react-sdk/pull/5139)
* Add communities v2 prototyping feature flag + initial tag panel prototypes
[\#5133](https://github.com/matrix-org/matrix-react-sdk/pull/5133)
* Remove some unused components
[\#5134](https://github.com/matrix-org/matrix-react-sdk/pull/5134)
* Allow avatar image view for 1:1 rooms
[\#5137](https://github.com/matrix-org/matrix-react-sdk/pull/5137)
* Send mx_local_settings in rageshake
[\#5136](https://github.com/matrix-org/matrix-react-sdk/pull/5136)
* Run all room leaving behaviour through a single function
[\#5132](https://github.com/matrix-org/matrix-react-sdk/pull/5132)
* Add clarifying comment in media device selection
[\#5131](https://github.com/matrix-org/matrix-react-sdk/pull/5131)
* Settings v3: Feature flag changes
[\#5124](https://github.com/matrix-org/matrix-react-sdk/pull/5124)
* Clear url previews if they all get edited out of the event
[\#5129](https://github.com/matrix-org/matrix-react-sdk/pull/5129)
* Consider tab completions as modifications for editing purposes to unlock
sending
[\#5128](https://github.com/matrix-org/matrix-react-sdk/pull/5128)
* Use matrix-doc for SAS emoji translations
[\#5125](https://github.com/matrix-org/matrix-react-sdk/pull/5125)
* Add a rageshake function to download the logs locally
[\#3849](https://github.com/matrix-org/matrix-react-sdk/pull/3849)
* Room List filtering visual tweaks
[\#5123](https://github.com/matrix-org/matrix-react-sdk/pull/5123)
* Make reply preview not an overlay so you can see new messages
[\#5072](https://github.com/matrix-org/matrix-react-sdk/pull/5072)
* Allow room tile context menu when minimized using right click
[\#5113](https://github.com/matrix-org/matrix-react-sdk/pull/5113)
* Add null guard to group inviter for corrupted groups
[\#5121](https://github.com/matrix-org/matrix-react-sdk/pull/5121)
* Room List styling tweaks
[\#5118](https://github.com/matrix-org/matrix-react-sdk/pull/5118)
* Fix corner rounding on images not always affecting right side
[\#5120](https://github.com/matrix-org/matrix-react-sdk/pull/5120)
* Change add room action for rooms to context menu
[\#5108](https://github.com/matrix-org/matrix-react-sdk/pull/5108)
* Switch out the globe icon and colour it depending on theme
[\#5106](https://github.com/matrix-org/matrix-react-sdk/pull/5106)
* Message Action Bar watch for event send changes
[\#5115](https://github.com/matrix-org/matrix-react-sdk/pull/5115)
* Put message previews for Emoji behind Labs
[\#5110](https://github.com/matrix-org/matrix-react-sdk/pull/5110)
* Fix styling for selected community marker
[\#5107](https://github.com/matrix-org/matrix-react-sdk/pull/5107)
* Fix action bar safe area regression
[\#5111](https://github.com/matrix-org/matrix-react-sdk/pull/5111)
* Fix /op slash command
[\#5109](https://github.com/matrix-org/matrix-react-sdk/pull/5109)
Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17) Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17)
=================================================================================================== ===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0) [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0)

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "3.2.0", "version": "3.3.0",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {
@ -163,9 +163,7 @@
"stylelint-config-standard": "^18.3.0", "stylelint-config-standard": "^18.3.0",
"stylelint-scss": "^3.18.0", "stylelint-scss": "^3.18.0",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"walk": "^2.3.14", "walk": "^2.3.14"
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12"
}, },
"jest": { "jest": {
"testMatch": [ "testMatch": [

View file

@ -70,6 +70,7 @@ interface IContent {
interface IThumbnail { interface IThumbnail {
info: { info: {
// eslint-disable-next-line camelcase
thumbnail_info: { thumbnail_info: {
w: number; w: number;
h: number; h: number;
@ -104,7 +105,12 @@ interface IAbortablePromise<T> extends Promise<T> {
* @return {Promise} A promise that resolves with an object with an info key * @return {Promise} A promise that resolves with an object with an info key
* and a thumbnail key. * and a thumbnail key.
*/ */
function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise<IThumbnail> { function createThumbnail(
element: ThumbnailableElement,
inputWidth: number,
inputHeight: number,
mimeType: string,
): Promise<IThumbnail> {
return new Promise((resolve) => { return new Promise((resolve) => {
let targetWidth = inputWidth; let targetWidth = inputWidth;
let targetHeight = inputHeight; let targetHeight = inputHeight;
@ -437,11 +443,13 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) { for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i]; const file = okFiles[i];
if (!uploadAll) { if (!uploadAll) {
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, { const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
file, '', UploadConfirmDialog, {
currentIndex: i, file,
totalFiles: okFiles.length, currentIndex: i,
}); totalFiles: okFiles.length,
},
);
const [shouldContinue, shouldUploadAll] = await finished; const [shouldContinue, shouldUploadAll] = await finished;
if (!shouldContinue) break; if (!shouldContinue) break;
if (shouldUploadAll) { if (shouldUploadAll) {

View file

@ -339,33 +339,9 @@ class HtmlHighlighter extends BaseHighlighter<string> {
} }
} }
class TextHighlighter extends BaseHighlighter<React.ReactNode> {
private key = 0;
/* create a <span> node to hold the given content
*
* snippet: content of the span
* highlight: true to highlight as a search match
*
* returns a React node
*/
protected processSnippet(snippet: string, highlight: boolean): React.ReactNode {
const key = this.key++;
let node = <span key={key} className={highlight ? this.highlightClass : null}>
{ snippet }
</span>;
if (highlight && this.highlightLink) {
node = <a key={key} href={this.highlightLink}>{ node }</a>;
}
return node;
}
}
interface IContent { interface IContent {
format?: string; format?: string;
// eslint-disable-next-line camelcase
formatted_body?: string; formatted_body?: string;
body: string; body: string;
} }
@ -474,8 +450,13 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
}); });
return isDisplayedWithHtml ? return isDisplayedWithHtml ?
<span key="body" ref={opts.ref} className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> : <span
<span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>; key="body"
ref={opts.ref}
className={className}
dangerouslySetInnerHTML={{ __html: safeBody }}
dir="auto"
/> : <span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
} }
/** /**

View file

@ -151,7 +151,7 @@ export class ModalManager {
prom: Promise<React.ComponentType>, prom: Promise<React.ComponentType>,
props?: IProps<T>, props?: IProps<T>,
className?: string, className?: string,
options?: IOptions<T> options?: IOptions<T>,
) { ) {
const modal: IModal<T> = { const modal: IModal<T> = {
onFinished: props ? props.onFinished : null, onFinished: props ? props.onFinished : null,
@ -182,7 +182,7 @@ export class ModalManager {
private getCloseFn<T extends any[]>( private getCloseFn<T extends any[]>(
modal: IModal<T>, modal: IModal<T>,
props: IProps<T> props: IProps<T>,
): [IHandle<T>["close"], IHandle<T>["finished"]] { ): [IHandle<T>["close"], IHandle<T>["finished"]] {
const deferred = defer<T>(); const deferred = defer<T>();
return [async (...args: T) => { return [async (...args: T) => {
@ -264,7 +264,7 @@ export class ModalManager {
className?: string, className?: string,
isPriorityModal = false, isPriorityModal = false,
isStaticModal = false, isStaticModal = false,
options: IOptions<T> = {} options: IOptions<T> = {},
): IHandle<T> { ): IHandle<T> {
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options); const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
if (isPriorityModal) { if (isPriorityModal) {
@ -287,7 +287,7 @@ export class ModalManager {
private appendDialogAsync<T extends any[]>( private appendDialogAsync<T extends any[]>(
prom: Promise<React.ComponentType>, prom: Promise<React.ComponentType>,
props?: IProps<T>, props?: IProps<T>,
className?: string className?: string,
): IHandle<T> { ): IHandle<T> {
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {}); const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});

View file

@ -860,12 +860,12 @@ export const Commands = [
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' + _t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' +
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' + ' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
'"%(fingerprint)s". This could mean your communications are being intercepted!', '"%(fingerprint)s". This could mean your communications are being intercepted!',
{ {
fprint, fprint,
userId, userId,
deviceId, deviceId,
fingerprint, fingerprint,
})); }));
} }
await cli.setDeviceVerified(userId, deviceId, true); await cli.setDeviceVerified(userId, deviceId, true);
@ -879,7 +879,7 @@ export const Commands = [
{ {
_t('The signing key you provided matches the signing key you received ' + _t('The signing key you provided matches the signing key you received ' +
'from %(userId)s\'s session %(deviceId)s. Session marked as verified.', 'from %(userId)s\'s session %(deviceId)s. Session marked as verified.',
{userId, deviceId}) {userId, deviceId})
} }
</p> </p>
</div>, </div>,

View file

@ -168,7 +168,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
key: Key.U, key: Key.U,
}], }],
description: _td("Upload a file"), description: _td("Upload a file"),
} },
], ],
[Categories.ROOM_LIST]: [ [Categories.ROOM_LIST]: [

View file

@ -190,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
} else if (onKeyDown) { } else if (onKeyDown) {
return onKeyDown(ev, state); return onKeyDown(ev, context.state);
} }
}, [context.state, onKeyDown, handleHomeEnd]); }, [context.state, onKeyDown, handleHomeEnd]);

View file

@ -30,6 +30,7 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
const target = ev.target as HTMLElement; const target = ev.target as HTMLElement;
let handled = true; let handled = true;
// HOME and END are handled by RovingTabIndexProvider
switch (ev.key) { switch (ev.key) {
case Key.ARROW_UP: case Key.ARROW_UP:
case Key.ARROW_DOWN: case Key.ARROW_DOWN:
@ -47,8 +48,6 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
} }
break; break;
// HOME and END are handled by RovingTabIndexProvider
default: default:
handled = false; handled = false;
} }

View file

@ -20,7 +20,7 @@ import React from "react";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> { interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
// whether or not the context menu is currently open // whether or not the context menu is currently open
isExpanded: boolean; isExpanded: boolean;
} }

View file

@ -20,7 +20,8 @@ import AccessibleTooltipButton from "../../components/views/elements/AccessibleT
import {useRovingTabIndex} from "../RovingTabIndex"; import {useRovingTabIndex} from "../RovingTabIndex";
import {Ref} from "./types"; import {Ref} from "./types";
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> { type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
interface IProps extends Omit<ATBProps, "onFocus" | "inputRef" | "tabIndex"> {
inputRef?: Ref; inputRef?: Ref;
} }

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from "react"; import React from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import {useRovingTabIndex} from "../RovingTabIndex"; import {useRovingTabIndex} from "../RovingTabIndex";
import {FocusHandler, Ref} from "./types"; import {FocusHandler, Ref} from "./types";

View file

@ -89,7 +89,11 @@ export default class CommandProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode { renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return ( return (
<div className="mx_Autocomplete_Completion_container_block" role="listbox" aria-label={_t("Command Autocomplete")}> <div
className="mx_Autocomplete_Completion_container_block"
role="listbox"
aria-label={_t("Command Autocomplete")}
>
{ completions } { completions }
</div> </div>
); );

View file

@ -91,15 +91,15 @@ export default class CommunityProvider extends AutocompleteProvider {
href: makeGroupPermalink(groupId), href: makeGroupPermalink(groupId),
component: ( component: (
<PillCompletion title={name} description={groupId}> <PillCompletion title={name} description={groupId}>
<BaseAvatar name={name || groupId} <BaseAvatar
width={24} name={name || groupId}
height={24} width={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} /> height={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
</PillCompletion> </PillCompletion>
), ),
range, range,
})) })).slice(0, 4);
.slice(0, 4);
} }
return completions; return completions;
} }

View file

@ -34,9 +34,9 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
const {title, subtitle, description, className, ...restProps} = props; const {title, subtitle, description, className, ...restProps} = props;
return ( return (
<div {...restProps} <div {...restProps}
className={classNames('mx_Autocomplete_Completion_block', className)} className={classNames('mx_Autocomplete_Completion_block', className)}
role="option" role="option"
ref={ref} ref={ref}
> >
<span className="mx_Autocomplete_Completion_title">{ title }</span> <span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span> <span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
@ -53,9 +53,9 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
const {title, subtitle, description, className, children, ...restProps} = props; const {title, subtitle, description, className, children, ...restProps} = props;
return ( return (
<div {...restProps} <div {...restProps}
className={classNames('mx_Autocomplete_Completion_pill', className)} className={classNames('mx_Autocomplete_Completion_pill', className)}
role="option" role="option"
ref={ref} ref={ref}
> >
{ children } { children }
<span className="mx_Autocomplete_Completion_title">{ title }</span> <span className="mx_Autocomplete_Completion_title">{ title }</span>

View file

@ -139,7 +139,11 @@ export default class EmojiProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode { renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return ( return (
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("Emoji Autocomplete")}> <div
className="mx_Autocomplete_Completion_container_pill"
role="listbox"
aria-label={_t("Emoji Autocomplete")}
>
{ completions } { completions }
</div> </div>
); );

View file

@ -110,9 +110,7 @@ export default class RoomProvider extends AutocompleteProvider {
), ),
range, range,
}; };
}) }).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
.filter((completion) => !!completion.completion && completion.completion.length > 0)
.slice(0, 4);
} }
return completions; return completions;
} }

View file

@ -71,8 +71,13 @@ export default class UserProvider extends AutocompleteProvider {
} }
} }
private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean, private onRoomTimeline = (
data: IRoomTimelineData) => { ev: MatrixEvent,
room: Room,
toStartOfTimeline: boolean,
removed: boolean,
data: IRoomTimelineData,
) => {
if (!room) return; if (!room) return;
if (removed) return; if (removed) return;
if (room.roomId !== this.room.roomId) return; if (room.roomId !== this.room.roomId) return;
@ -171,7 +176,11 @@ export default class UserProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode { renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return ( return (
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("User Autocomplete")}> <div
className="mx_Autocomplete_Completion_container_pill"
role="listbox"
aria-label={_t("User Autocomplete")}
>
{ completions } { completions }
</div> </div>
); );

View file

@ -1,90 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019, 2020 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig';
export default createReactClass({
displayName: 'CompatibilityPage',
propTypes: {
onAccept: PropTypes.func,
},
getDefaultProps: function() {
return {
onAccept: function() {}, // NOP
};
},
onAccept: function() {
this.props.onAccept();
},
render: function() {
const brand = SdkConfig.get().brand;
return (
<div className="mx_CompatibilityPage">
<div className="mx_CompatibilityPage_box">
<p>{_t(
"Sorry, your browser is <b>not</b> able to run %(brand)s.",
{
brand,
},
{
'b': (sub) => <b>{sub}</b>,
})
}</p>
<p>
{ _t(
"%(brand)s uses many advanced browser features, some of which are not available " +
"or experimental in your current browser.",
{ brand },
) }
</p>
<p>
{ _t(
'Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, ' +
'or <safariLink>Safari</safariLink> for the best experience.',
{},
{
'chromeLink': (sub) => <a href="https://www.google.com/chrome">{sub}</a>,
'firefoxLink': (sub) => <a href="https://firefox.com">{sub}</a>,
'safariLink': (sub) => <a href="https://apple.com/safari">{sub}</a>,
},
)}
</p>
<p>
{ _t(
"With your current browser, the look and feel of the application may be " +
"completely incorrect, and some or all features may not function. " +
"If you want to try it anyway you can continue, but you are on your own in terms " +
"of any issues you may encounter!",
) }
</p>
<button onClick={this.onAccept}>
{ _t("I understand the risks and wish to continue") }
</button>
</div>
</div>
);
},
});

View file

@ -233,8 +233,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
switch (ev.key) { switch (ev.key) {
case Key.TAB: case Key.TAB:
case Key.ESCAPE: case Key.ESCAPE:
// close on left and right arrows too for when it is a context menu on a <Toolbar /> case Key.ARROW_LEFT: // close on left and right arrows too for when it is a context menu on a <Toolbar />
case Key.ARROW_LEFT:
case Key.ARROW_RIGHT: case Key.ARROW_RIGHT:
this.props.onFinished(); this.props.onFinished();
break; break;

View file

@ -377,7 +377,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
public render(): React.ReactNode { public render(): React.ReactNode {
const tagPanel = !this.state.showTagPanel ? null : ( const tagPanel = !this.state.showTagPanel ? null : (
<div className="mx_LeftPanel_tagPanelContainer"> <div className="mx_LeftPanel_tagPanelContainer">
<TagPanel/> <TagPanel />
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null} {SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
</div> </div>
); );

View file

@ -43,11 +43,11 @@ import PlatformPeg from "../../PlatformPeg";
import { DefaultTagID } from "../../stores/room-list/models"; import { DefaultTagID } from "../../stores/room-list/models";
import { import {
showToast as showSetPasswordToast, showToast as showSetPasswordToast,
hideToast as hideSetPasswordToast hideToast as hideSetPasswordToast,
} from "../../toasts/SetPasswordToast"; } from "../../toasts/SetPasswordToast";
import { import {
showToast as showServerLimitToast, showToast as showServerLimitToast,
hideToast as hideServerLimitToast hideToast as hideServerLimitToast,
} from "../../toasts/ServerLimitToast"; } from "../../toasts/ServerLimitToast";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import LeftPanel from "./LeftPanel"; import LeftPanel from "./LeftPanel";
@ -79,6 +79,7 @@ interface IProps {
initialEventPixelOffset: number; initialEventPixelOffset: number;
leftDisabled: boolean; leftDisabled: boolean;
rightDisabled: boolean; rightDisabled: boolean;
// eslint-disable-next-line camelcase
page_type: string; page_type: string;
autoJoin: boolean; autoJoin: boolean;
thirdPartyInvite?: object; thirdPartyInvite?: object;
@ -98,7 +99,9 @@ interface IProps {
} }
interface IUsageLimit { interface IUsageLimit {
// eslint-disable-next-line camelcase
limit_type: "monthly_active_user" | string; limit_type: "monthly_active_user" | string;
// eslint-disable-next-line camelcase
admin_contact?: string; admin_contact?: string;
} }
@ -316,10 +319,10 @@ class LoggedInView extends React.Component<IProps, IState> {
} }
}; };
_calculateServerLimitToast(syncErrorData: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) { _calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED"; const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) { if (error) {
usageLimitEventContent = syncErrorData.error.data; usageLimitEventContent = syncError.error.data;
} }
if (usageLimitEventContent) { if (usageLimitEventContent) {
@ -620,18 +623,18 @@ class LoggedInView extends React.Component<IProps, IState> {
switch (this.props.page_type) { switch (this.props.page_type) {
case PageTypes.RoomView: case PageTypes.RoomView:
pageElement = <RoomView pageElement = <RoomView
ref={this._roomView} ref={this._roomView}
autoJoin={this.props.autoJoin} autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered} onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite} thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData} oobData={this.props.roomOobData}
viaServers={this.props.viaServers} viaServers={this.props.viaServers}
eventPixelOffset={this.props.initialEventPixelOffset} eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'} key={this.props.currentRoomId || 'roomview'}
disabled={this.props.middleDisabled} disabled={this.props.middleDisabled}
ConferenceHandler={this.props.ConferenceHandler} ConferenceHandler={this.props.ConferenceHandler}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
/>; />;
break; break;
case PageTypes.MyGroups: case PageTypes.MyGroups:

View file

@ -69,7 +69,7 @@ import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { import {
showToast as showAnalyticsToast, showToast as showAnalyticsToast,
hideToast as hideAnalyticsToast hideToast as hideAnalyticsToast,
} from "../../toasts/AnalyticsToast"; } from "../../toasts/AnalyticsToast";
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast"; import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
@ -129,6 +129,7 @@ interface IScreen {
params?: object; params?: object;
} }
/* eslint-disable camelcase */
interface IRoomInfo { interface IRoomInfo {
room_id?: string; room_id?: string;
room_alias?: string; room_alias?: string;
@ -140,6 +141,7 @@ interface IRoomInfo {
oob_data?: object; oob_data?: object;
via_servers?: string[]; via_servers?: string[];
} }
/* eslint-enable camelcase */
interface IProps { // TODO type things better interface IProps { // TODO type things better
config: Record<string, any>; config: Record<string, any>;
@ -165,6 +167,7 @@ interface IState {
// the master view we are showing. // the master view we are showing.
view: Views; view: Views;
// What the LoggedInView would be showing if visible // What the LoggedInView would be showing if visible
// eslint-disable-next-line camelcase
page_type?: PageTypes; page_type?: PageTypes;
// The ID of the room we're viewing. This is either populated directly // The ID of the room we're viewing. This is either populated directly
// in the case where we view a room by ID or by RoomView when it resolves // in the case where we view a room by ID or by RoomView when it resolves
@ -180,8 +183,11 @@ interface IState {
middleDisabled: boolean; middleDisabled: boolean;
// the right panel's disabled state is tracked in its store. // the right panel's disabled state is tracked in its store.
// Parameters used in the registration dance with the IS // Parameters used in the registration dance with the IS
// eslint-disable-next-line camelcase
register_client_secret?: string; register_client_secret?: string;
// eslint-disable-next-line camelcase
register_session_id?: string; register_session_id?: string;
// eslint-disable-next-line camelcase
register_id_sid?: string; register_id_sid?: string;
// When showing Modal dialogs we need to set aria-hidden on the root app element // When showing Modal dialogs we need to set aria-hidden on the root app element
// and disable it when there are no dialogs // and disable it when there are no dialogs
@ -341,6 +347,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage // TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
// eslint-disable-next-line camelcase
UNSAFE_componentWillUpdate(props, state) { UNSAFE_componentWillUpdate(props, state) {
if (this.shouldTrackPageChange(this.state, state)) { if (this.shouldTrackPageChange(this.state, state)) {
this.startPageChangeTimer(); this.startPageChangeTimer();
@ -610,8 +617,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog"); const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
{initialTabId: tabPayload.initialTabId}, {initialTabId: tabPayload.initialTabId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
);
// View the welcome or home page if we need something to look at // View the welcome or home page if we need something to look at
this.viewSomethingBehindModal(); this.viewSomethingBehindModal();
@ -1080,7 +1086,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
title: _t("Leave room"), title: _t("Leave room"),
description: ( description: (
<span> <span>
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) } { _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
{ warnings } { warnings }
</span> </span>
), ),
@ -1433,7 +1439,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
cli.on("crypto.warning", (type) => { cli.on("crypto.warning", (type) => {
switch (type) { switch (type) {
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED': case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
const brand = SdkConfig.get().brand;
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, { Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Old cryptography data detected'), title: _t('Old cryptography data detected'),
description: _t( description: _t(
@ -1444,7 +1449,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
"in this version. This may also cause messages exchanged with this " + "in this version. This may also cause messages exchanged with this " +
"version to fail. If you experience problems, log out and back in " + "version to fail. If you experience problems, log out and back in " +
"again. To retain message history, export and re-import your keys.", "again. To retain message history, export and re-import your keys.",
{ brand }, { brand: SdkConfig.get().brand },
), ),
}); });
break; break;

View file

@ -20,7 +20,6 @@ import classNames from "classnames";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import { ActionPayload } from "../../dispatcher/payloads"; import { ActionPayload } from "../../dispatcher/payloads";
import { throttle } from 'lodash';
import { Key } from "../../Keyboard"; import { Key } from "../../Keyboard";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
@ -137,7 +136,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
}); });
let icon = ( let icon = (
<div className='mx_RoomSearch_icon'/> <div className='mx_RoomSearch_icon' />
); );
let input = ( let input = (
<input <input

View file

@ -18,7 +18,6 @@ limitations under the License.
import * as React from "react"; import * as React from "react";
import {_t} from '../../languageHandler'; import {_t} from '../../languageHandler';
import * as PropTypes from "prop-types";
import * as sdk from "../../index"; import * as sdk from "../../index";
import AutoHideScrollbar from './AutoHideScrollbar'; import AutoHideScrollbar from './AutoHideScrollbar';
import { ReactNode } from "react"; import { ReactNode } from "react";

View file

@ -40,7 +40,7 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { SettingLevel } from "../../settings/SettingLevel"; import { SettingLevel } from "../../settings/SettingLevel";
import IconizedContextMenu, { import IconizedContextMenu, {
IconizedContextMenuOption, IconizedContextMenuOption,
IconizedContextMenuOptionList IconizedContextMenuOptionList,
} from "../views/context_menus/IconizedContextMenu"; } from "../views/context_menus/IconizedContextMenu";
interface IProps { interface IProps {
@ -234,12 +234,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
> >
<div className="mx_UserMenu_contextMenu_header"> <div className="mx_UserMenu_contextMenu_header">
<div className="mx_UserMenu_contextMenu_name"> <div className="mx_UserMenu_contextMenu_name">
<span className="mx_UserMenu_contextMenu_displayName"> <span className="mx_UserMenu_contextMenu_displayName">
{OwnProfileStore.instance.displayName} {OwnProfileStore.instance.displayName}
</span> </span>
<span className="mx_UserMenu_contextMenu_userId"> <span className="mx_UserMenu_contextMenu_userId">
{MatrixClientPeg.get().getUserId()} {MatrixClientPeg.get().getUserId()}
</span> </span>
</div> </div>
<AccessibleTooltipButton <AccessibleTooltipButton
className="mx_UserMenu_contextMenu_themeButton" className="mx_UserMenu_contextMenu_themeButton"

View file

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; import React, {useCallback, useContext, useEffect, useState} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import * as AvatarLogic from '../../../Avatar'; import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -96,7 +96,7 @@ const BaseAvatar = (props: IProps) => {
urls, urls,
width = 40, width = 40,
height = 40, height = 40,
resizeMethod = "crop", // eslint-disable-line no-unused-vars resizeMethod = "crop", // eslint-disable-line @typescript-eslint/no-unused-vars
defaultToInitialLetter = true, defaultToInitialLetter = true,
onClick, onClick,
inputRef, inputRef,

View file

@ -126,7 +126,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
private onPresenceUpdate = () => { private onPresenceUpdate = () => {
if (this.isUnmounted) return; if (this.isUnmounted) return;
let newIcon = this.getPresenceIcon(); const newIcon = this.getPresenceIcon();
if (newIcon !== this.state.icon) this.setState({icon: newIcon}); if (newIcon !== this.state.icon) this.setState({icon: newIcon});
}; };

View file

@ -47,7 +47,7 @@ export default class GroupAvatar extends React.Component<IProps> {
render() { render() {
// extract the props we use from props so we can pass any others through // extract the props we use from props so we can pass any others through
// should consider adding this as a global rule in js-sdk? // should consider adding this as a global rule in js-sdk?
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props; const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
return ( return (

View file

@ -25,4 +25,4 @@ const PulsedAvatar: React.FC<IProps> = (props) => {
</div>; </div>;
}; };
export default PulsedAvatar; export default PulsedAvatar;

View file

@ -1,57 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
const Presets = {
PrivateChat: "private_chat",
PublicChat: "public_chat",
Custom: "custom",
};
export default createReactClass({
displayName: 'CreateRoomPresets',
propTypes: {
onChange: PropTypes.func,
preset: PropTypes.string,
},
Presets: Presets,
getDefaultProps: function() {
return {
onChange: function() {},
};
},
onValueChanged: function(ev) {
this.props.onChange(ev.target.value);
},
render: function() {
return (
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
<option value={this.Presets.PrivateChat}>{ _t("Private Chat") }</option>
<option value={this.Presets.PublicChat}>{ _t("Public Chat") }</option>
<option value={this.Presets.Custom}>{ _t("Custom") }</option>
</select>
);
},
});

View file

@ -1,106 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'RoomAlias',
propTypes: {
// Specifying a homeserver will make magical things happen when you,
// e.g. start typing in the room alias box.
homeserver: PropTypes.string,
alias: PropTypes.string,
onChange: PropTypes.func,
},
getDefaultProps: function() {
return {
onChange: function() {},
alias: '',
};
},
getAliasLocalpart: function() {
let room_alias = this.props.alias;
if (room_alias && this.props.homeserver) {
const suffix = ":" + this.props.homeserver;
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
room_alias = room_alias.slice(1, -suffix.length);
}
}
return room_alias;
},
onValueChanged: function(ev) {
this.props.onChange(ev.target.value);
},
onFocus: function(ev) {
const target = ev.target;
const curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "") {
const self = this;
setTimeout(function() {
target.value = "#:" + self.props.homeserver;
target.setSelectionRange(1, 1);
}, 0);
} else {
const suffix = ":" + this.props.homeserver;
setTimeout(function() {
target.setSelectionRange(
curr_val.startsWith("#") ? 1 : 0,
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length,
);
}, 0);
}
}
},
onBlur: function(ev) {
const curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "#:" + this.props.homeserver) {
ev.target.value = "";
return;
}
if (curr_val != "") {
let new_val = ev.target.value;
const suffix = ":" + this.props.homeserver;
if (!curr_val.startsWith("#")) new_val = "#" + new_val;
if (!curr_val.endsWith(suffix)) new_val = new_val + suffix;
ev.target.value = new_val;
}
}
},
render: function() {
return (
<input type="text" className="mx_RoomAlias" placeholder={_t("Address (optional)")}
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
value={this.props.alias} />
);
},
});

View file

@ -21,9 +21,6 @@ import { IDialogProps } from "./IDialogProps";
import Field from "../elements/Field"; import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import InfoTooltip from "../elements/InfoTooltip";
import dis from "../../../dispatcher/dispatcher";
import {showCommunityRoomInviteDialog} from "../../../RoomInvite";
import { arrayFastClone } from "../../../utils/arrays"; import { arrayFastClone } from "../../../utils/arrays";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
@ -31,7 +28,6 @@ import InviteDialog from "./InviteDialog";
import BaseAvatar from "../avatars/BaseAvatar"; import BaseAvatar from "../avatars/BaseAvatar";
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite"; import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite";
import {humanizeTime} from "../../../utils/humanize";
import StyledCheckbox from "../elements/StyledCheckbox"; import StyledCheckbox from "../elements/StyledCheckbox";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import ErrorDialog from "./ErrorDialog"; import ErrorDialog from "./ErrorDialog";
@ -171,7 +167,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
public render() { public render() {
const emailAddresses = []; const emailAddresses = [];
this.state.emailTargets.forEach((address, i) => { this.state.emailTargets.forEach((address, i) => {
emailAddresses.push( emailAddresses.push((
<Field <Field
key={i} key={i}
value={address} value={address}
@ -180,11 +176,11 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
placeholder={_t("Email address")} placeholder={_t("Email address")}
onBlur={() => this.onAddressBlur(i)} onBlur={() => this.onAddressBlur(i)}
/> />
); ));
}); });
// Push a clean input // Push a clean input
emailAddresses.push( emailAddresses.push((
<Field <Field
key={emailAddresses.length} key={emailAddresses.length}
value={""} value={""}
@ -192,23 +188,23 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")} label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")} placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
/> />
); ));
let peopleIntro = null; let peopleIntro = null;
let people = []; const people = [];
if (this.state.showPeople) { if (this.state.showPeople) {
const humansToPresent = this.state.people.slice(0, this.state.numPeople); const humansToPresent = this.state.people.slice(0, this.state.numPeople);
humansToPresent.forEach((person, i) => { humansToPresent.forEach((person, i) => {
people.push(this.renderPerson(person, i)); people.push(this.renderPerson(person, i));
}); });
if (humansToPresent.length < this.state.people.length) { if (humansToPresent.length < this.state.people.length) {
people.push( people.push((
<AccessibleButton <AccessibleButton
onClick={this.onShowMorePeople} onClick={this.onShowMorePeople}
kind="link" key="more" kind="link" key="more"
className="mx_CommunityPrototypeInviteDialog_morePeople" className="mx_CommunityPrototypeInviteDialog_morePeople"
>{_t("Show more")}</AccessibleButton> >{_t("Show more")}</AccessibleButton>
); ));
} }
} }
if (this.state.people.length > 0) { if (this.state.people.length > 0) {

View file

@ -163,8 +163,9 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
</span> </span>
); );
if (this.state.error) { if (this.state.error) {
const classes = "mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error";
helpText = ( helpText = (
<span className="mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error"> <span className={classes}>
{this.state.error} {this.state.error}
</span> </span>
); );
@ -205,7 +206,10 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
ref={this.avatarUploadRef} accept="image/*" ref={this.avatarUploadRef} accept="image/*"
onChange={this.onAvatarChanged} onChange={this.onAvatarChanged}
/> />
<AccessibleButton onClick={this.onChangeAvatar} className="mx_CreateCommunityPrototypeDialog_avatarContainer"> <AccessibleButton
onClick={this.onChangeAvatar}
className="mx_CreateCommunityPrototypeDialog_avatarContainer"
>
{preview} {preview}
</AccessibleButton> </AccessibleButton>
<div className="mx_CreateCommunityPrototypeDialog_tip"> <div className="mx_CreateCommunityPrototypeDialog_tip">

View file

@ -186,8 +186,8 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
title = _t('Share Room Message'); title = _t('Share Room Message');
checkbox = <div> checkbox = <div>
<StyledCheckbox <StyledCheckbox
checked={this.state.linkSpecificEvent} checked={this.state.linkSpecificEvent}
onClick={this.onLinkSpecificEventCheckboxClick} onClick={this.onLinkSpecificEventCheckboxClick}
> >
{ _t('Link to selected message') } { _t('Link to selected message') }
</StyledCheckbox> </StyledCheckbox>
@ -198,16 +198,18 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
const encodedUrl = encodeURIComponent(matrixToUrl); const encodedUrl = encodeURIComponent(matrixToUrl);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return <BaseDialog title={title} return <BaseDialog
className='mx_ShareDialog' title={title}
contentId='mx_Dialog_content' className='mx_ShareDialog'
onFinished={this.props.onFinished} contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
> >
<div className="mx_ShareDialog_content"> <div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_matrixto"> <div className="mx_ShareDialog_matrixto">
<a href={matrixToUrl} <a
onClick={ShareDialog.onLinkClick} href={matrixToUrl}
className="mx_ShareDialog_matrixto_link" onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
> >
{ matrixToUrl } { matrixToUrl }
</a> </a>

View file

@ -34,7 +34,6 @@ export interface ILocationState {
} }
export default class Draggable extends React.Component<IProps, IState> { export default class Draggable extends React.Component<IProps, IState> {
constructor(props: IProps) { constructor(props: IProps) {
super(props); super(props);
@ -77,5 +76,4 @@ export default class Draggable extends React.Component<IProps, IState> {
render() { render() {
return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />; return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />;
} }
}
}

View file

@ -39,11 +39,13 @@ interface IProps {
className: string; className: string;
} }
/* eslint-disable camelcase */
interface IState { interface IState {
userId: string; userId: string;
displayname: string; displayname: string;
avatar_url: string; avatar_url: string;
} }
/* eslint-enable camelcase */
const AVATAR_SIZE = 32; const AVATAR_SIZE = 32;
@ -63,19 +65,18 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const userId = client.getUserId(); const userId = client.getUserId();
const profileInfo = await client.getProfileInfo(userId); const profileInfo = await client.getProfileInfo(userId);
const avatar_url = Avatar.avatarUrlForUser( const avatarUrl = Avatar.avatarUrlForUser(
{avatarUrl: profileInfo.avatar_url}, {avatarUrl: profileInfo.avatar_url},
AVATAR_SIZE, AVATAR_SIZE, "crop"); AVATAR_SIZE, AVATAR_SIZE, "crop");
this.setState({ this.setState({
userId, userId,
displayname: profileInfo.displayname, displayname: profileInfo.displayname,
avatar_url, avatar_url: avatarUrl,
}); });
} }
private fakeEvent({userId, displayname, avatar_url}: IState) { private fakeEvent({userId, displayname, avatar_url: avatarUrl}: IState) {
// Fake it till we make it // Fake it till we make it
const event = new MatrixEvent(JSON.parse(`{ const event = new MatrixEvent(JSON.parse(`{
"type": "m.room.message", "type": "m.room.message",
@ -85,12 +86,12 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
"msgtype": "m.text", "msgtype": "m.text",
"body": "${this.props.message}", "body": "${this.props.message}",
"displayname": "${displayname}", "displayname": "${displayname}",
"avatar_url": "${avatar_url}" "avatar_url": "${avatarUrl}"
}, },
"msgtype": "m.text", "msgtype": "m.text",
"body": "${this.props.message}", "body": "${this.props.message}",
"displayname": "${displayname}", "displayname": "${displayname}",
"avatar_url": "${avatar_url}" "avatar_url": "${avatarUrl}"
}, },
"unsigned": { "unsigned": {
"age": 97 "age": 97
@ -104,7 +105,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
name: displayname, name: displayname,
userId: userId, userId: userId,
getAvatarUrl: (..._) => { getAvatarUrl: (..._) => {
return avatar_url; return avatarUrl;
}, },
}; };
@ -114,13 +115,10 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
public render() { public render() {
const event = this.fakeEvent(this.state); const event = this.fakeEvent(this.state);
let className = classnames( const className = classnames(this.props.className, {
this.props.className, "mx_IRCLayout": this.props.useIRCLayout,
{ "mx_GroupLayout": !this.props.useIRCLayout,
"mx_IRCLayout": this.props.useIRCLayout, });
"mx_GroupLayout": !this.props.useIRCLayout,
}
);
return <div className={className}> return <div className={className}>
<EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} /> <EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} />

View file

@ -198,11 +198,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
} }
} }
public render() { public render() {
const { /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
element, prefixComponent, postfixComponent, className, onValidate, children, const { element, prefixComponent, postfixComponent, className, onValidate, children,
tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props; tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props;
// Set some defaults for the <input> element // Set some defaults for the <input> element

View file

@ -78,7 +78,12 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
private onMoueUp(event: MouseEvent) { private onMoueUp(event: MouseEvent) {
if (this.props.roomId) { if (this.props.roomId) {
SettingsStore.setValue("ircDisplayNameWidth", this.props.roomId, SettingLevel.ROOM_DEVICE, this.state.width); SettingsStore.setValue(
"ircDisplayNameWidth",
this.props.roomId,
SettingLevel.ROOM_DEVICE,
this.state.width,
);
} }
} }

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Tooltip from './Tooltip'; import Tooltip from './Tooltip';
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";

View file

@ -41,7 +41,7 @@ const QRCode: React.FC<IProps> = ({data, className, ...options}) => {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [JSON.stringify(data), options]); }, [JSON.stringify(data), options]); // eslint-disable-line react-hooks/exhaustive-deps
return <div className={classNames("mx_QRCode", className)}> return <div className={classNames("mx_QRCode", className)}>
{ dataUri ? <img src={dataUri} className="mx_VerificationQRCode" alt={_t("QR Code")} /> : <Spinner /> } { dataUri ? <img src={dataUri} className="mx_VerificationQRCode" alt={_t("QR Code")} /> : <Spinner /> }

View file

@ -45,7 +45,7 @@ export default class Slider extends React.Component<IProps> {
// non linear slider. // non linear slider.
private offset(values: number[], value: number): number { private offset(values: number[], value: number): number {
// the index of the first number greater than value. // the index of the first number greater than value.
let closest = values.reduce((prev, curr) => { const closest = values.reduce((prev, curr) => {
return (value > curr ? prev + 1 : prev); return (value > curr ? prev + 1 : prev);
}, 0); }, 0);
@ -68,17 +68,16 @@ export default class Slider extends React.Component<IProps> {
const linearInterpolation = (value - closestLessValue) / (closestGreaterValue - closestLessValue); const linearInterpolation = (value - closestLessValue) / (closestGreaterValue - closestLessValue);
return 100 * (closest - 1 + linearInterpolation) * intervalWidth; return 100 * (closest - 1 + linearInterpolation) * intervalWidth;
} }
render(): React.ReactNode { render(): React.ReactNode {
const dots = this.props.values.map(v => const dots = this.props.values.map(v => <Dot
<Dot active={v <= this.props.value} active={v <= this.props.value}
label={this.props.displayFunc(v)} label={this.props.displayFunc(v)}
onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)} onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)}
key={v} key={v}
disabled={this.props.disabled} disabled={this.props.disabled}
/>); />);
let selection = null; let selection = null;
@ -93,7 +92,7 @@ export default class Slider extends React.Component<IProps> {
return <div className="mx_Slider"> return <div className="mx_Slider">
<div> <div>
<div className="mx_Slider_bar"> <div className="mx_Slider_bar">
<hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)}/> <hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)} />
{ selection } { selection }
</div> </div>
<div className="mx_Slider_dotContainer"> <div className="mx_Slider_dotContainer">

View file

@ -17,8 +17,6 @@ limitations under the License.
import React from "react"; import React from "react";
import { randomString } from "matrix-js-sdk/src/randomstring"; import { randomString } from "matrix-js-sdk/src/randomstring";
const CHECK_BOX_SVG = require("../../../../res/img/feather-customised/check.svg");
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> { interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
} }
@ -39,13 +37,14 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
} }
public render() { public render() {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { children, className, ...otherProps } = this.props; const { children, className, ...otherProps } = this.props;
return <span className={"mx_Checkbox " + className}> return <span className={"mx_Checkbox " + className}>
<input id={this.id} {...otherProps} type="checkbox" /> <input id={this.id} {...otherProps} type="checkbox" />
<label htmlFor={this.id}> <label htmlFor={this.id}>
{/* Using the div to center the image */} {/* Using the div to center the image */}
<div className="mx_Checkbox_background"> <div className="mx_Checkbox_background">
<img src={CHECK_BOX_SVG}/> <img src={require("../../../../res/img/feather-customised/check.svg")} />
</div> </div>
<div> <div>
{ this.props.children } { this.props.children }
@ -53,4 +52,4 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
</label> </label>
</span>; </span>;
} }
} }

View file

@ -1,76 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket 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.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'UserSelector',
propTypes: {
onChange: PropTypes.func,
selected_users: PropTypes.arrayOf(PropTypes.string),
},
getDefaultProps: function() {
return {
onChange: function() {},
selected: [],
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._user_id_input = createRef();
},
addUser: function(user_id) {
if (this.props.selected_users.indexOf(user_id == -1)) {
this.props.onChange(this.props.selected_users.concat([user_id]));
}
},
removeUser: function(user_id) {
this.props.onChange(this.props.selected_users.filter(function(e) {
return e != user_id;
}));
},
onAddUserId: function() {
this.addUser(this._user_id_input.current.value);
this._user_id_input.current.value = "";
},
render: function() {
const self = this;
return (
<div>
<ul className="mx_UserSelector_UserIdList">
{ this.props.selected_users.map(function(user_id, i) {
return <li key={user_id}>{ user_id } - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
}) }
</ul>
<input type="text" ref={this._user_id_input} defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")} />
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
{ _t("Add User") }
</button>
</div>
);
},
});

View file

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, {forwardRef} from "react";
export default ({mxEvent}) => { export default forwardRef(({mxEvent}, ref) => {
const text = mxEvent.getContent().body; const text = mxEvent.getContent().body;
return ( return (
<span className="mx_UnknownBody"> <span className="mx_UnknownBody" ref={ref}>
{ text } { text }
</span> </span>
); );
}; });

View file

@ -76,14 +76,16 @@ const EncryptionInfo: React.FC<IProps> = ({
description = ( description = (
<div> <div>
<p>{_t("Messages in this room are end-to-end encrypted.")}</p> <p>{_t("Messages in this room are end-to-end encrypted.")}</p>
<p>{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p> <p>{_t("Your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.")}</p>
</div> </div>
); );
} else { } else {
description = ( description = (
<div> <div>
<p>{_t("Messages in this room are not end-to-end encrypted.")}</p> <p>{_t("Messages in this room are not end-to-end encrypted.")}</p>
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p> <p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.")}</p>
</div> </div>
); );
} }

View file

@ -23,7 +23,10 @@ import dis from '../../../dispatcher/dispatcher';
import RightPanelStore from "../../../stores/RightPanelStore"; import RightPanelStore from "../../../stores/RightPanelStore";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from '../../../dispatcher/actions'; import {Action} from '../../../dispatcher/actions';
import {SetRightPanelPhasePayload, SetRightPanelPhaseRefireParams} from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; import {
SetRightPanelPhasePayload,
SetRightPanelPhaseRefireParams,
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import {EventSubscription} from "fbemitter"; import {EventSubscription} from "fbemitter";
export enum HeaderKind { export enum HeaderKind {
@ -38,7 +41,7 @@ interface IState {
interface IProps {} interface IProps {}
export default class HeaderButtons extends React.Component<IProps, IState> { export default abstract class HeaderButtons extends React.Component<IProps, IState> {
private storeToken: EventSubscription; private storeToken: EventSubscription;
private dispatcherRef: string; private dispatcherRef: string;
@ -92,14 +95,7 @@ export default class HeaderButtons extends React.Component<IProps, IState> {
} }
// XXX: Make renderButtons a prop // XXX: Make renderButtons a prop
public renderButtons(): JSX.Element[] { public abstract renderButtons(): JSX.Element[];
// Ignore - intended to be overridden by subclasses
// Return empty fragment to satisfy the type
return [
<React.Fragment>
</React.Fragment>
];
}
public render() { public render() {
// inline style as this will be swapped around in future commits // inline style as this will be swapped around in future commits

View file

@ -30,8 +30,6 @@ import {_t} from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import E2EIcon from "../rooms/E2EIcon"; import E2EIcon from "../rooms/E2EIcon";
import { import {
PHASE_UNSENT,
PHASE_REQUESTED,
PHASE_READY, PHASE_READY,
PHASE_DONE, PHASE_DONE,
PHASE_STARTED, PHASE_STARTED,
@ -104,14 +102,15 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
</div>; </div>;
} }
if (showSAS) { if (showSAS) {
sasBlockDialog = sasBlockDialog = <div className='mx_VerificationPanel_QRPhase_startOption'>
<div className='mx_VerificationPanel_QRPhase_startOption'> <p>{_t("Compare unique emoji")}</p>
<p>{_t("Compare unique emoji")}</p> <span className='mx_VerificationPanel_QRPhase_helpText'>
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span> {_t("Compare a unique set of emoji if you don't have a camera on either device")}
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'> </span>
{_t("Start")} <AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
</AccessibleButton> {_t("Start")}
</div>; </AccessibleButton>
</div>;
} }
const or = qrBlockDialog && sasBlockDialog ? const or = qrBlockDialog && sasBlockDialog ?
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null; <div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
@ -165,8 +164,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} }
const noCommonMethodBlock = noCommonMethodError ? const noCommonMethodBlock = noCommonMethodError ?
<div className="mx_UserInfo_container">{noCommonMethodError}</div> : <div className="mx_UserInfo_container">{noCommonMethodError}</div> :
null; null;
// TODO: add way to open camera to scan a QR code // TODO: add way to open camera to scan a QR code
return <React.Fragment> return <React.Fragment>

View file

@ -92,6 +92,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
}; };
public render(): React.ReactElement { public render(): React.ReactElement {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const {notification, forceCount, roomId, onClick, ...props} = this.props; const {notification, forceCount, roomId, onClick, ...props} = this.props;
// Don't show a badge if we don't need to // Don't show a badge if we don't need to

View file

@ -218,7 +218,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
private getRoomDelta = (roomId: string, delta: number, unread = false) => { private getRoomDelta = (roomId: string, delta: number, unread = false) => {
const lists = RoomListStore.instance.orderedLists; const lists = RoomListStore.instance.orderedLists;
let rooms: Room = []; const rooms: Room = [];
TAG_ORDER.forEach(t => { TAG_ORDER.forEach(t => {
let listRooms = lists[t]; let listRooms = lists[t];
@ -290,7 +290,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
// TODO: Put community invites in a more sensible place (not in the room list) // TODO: Put community invites in a more sensible place (not in the room list)
// See https://github.com/vector-im/element-web/issues/14456 // See https://github.com/vector-im/element-web/issues/14456
return MatrixClientPeg.get().getGroups().filter(g => { return MatrixClientPeg.get().getGroups().filter(g => {
return g.myMembership === 'invite'; return g.myMembership === 'invite';
}).map(g => { }).map(g => {
const avatar = ( const avatar = (
<GroupAvatar <GroupAvatar
@ -346,21 +346,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
: TAG_AESTHETICS[orderedTagId]; : TAG_AESTHETICS[orderedTagId];
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`); if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
components.push( components.push(<RoomSublist
<RoomSublist key={`sublist-${orderedTagId}`}
key={`sublist-${orderedTagId}`} tagId={orderedTagId}
tagId={orderedTagId} forRooms={true}
forRooms={true} startAsHidden={aesthetics.defaultHidden}
startAsHidden={aesthetics.defaultHidden} label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)} onAddRoom={aesthetics.onAddRoom}
onAddRoom={aesthetics.onAddRoom} addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel} addRoomContextMenu={aesthetics.addRoomContextMenu}
addRoomContextMenu={aesthetics.addRoomContextMenu} isMinimized={this.props.isMinimized}
isMinimized={this.props.isMinimized} onResize={this.props.onResize}
onResize={this.props.onResize} extraBadTilesThatShouldntExist={extraTiles}
extraBadTilesThatShouldntExist={extraTiles} />);
/>
);
} }
return components; return components;

View file

@ -1,80 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'RoomNameEditor',
propTypes: {
room: PropTypes.object.isRequired,
},
getInitialState: function() {
return {
name: null,
};
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
const room = this.props.room;
const name = room.currentState.getStateEvents('m.room.name', '');
const myId = MatrixClientPeg.get().credentials.userId;
const defaultName = room.getDefaultRoomName(myId);
this.setState({
name: name ? name.getContent().name : '',
});
this._placeholderName = _t("Unnamed Room");
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
this._placeholderName += " (" + defaultName + ")";
}
},
getRoomName: function() {
return this.state.name;
},
_onValueChanged: function(value, shouldSubmit) {
this.setState({
name: value,
});
},
render: function() {
const EditableText = sdk.getComponent("elements.EditableText");
return (
<div className="mx_RoomHeader_name">
<EditableText
className="mx_RoomHeader_nametext mx_RoomHeader_editable"
placeholderClassName="mx_RoomHeader_placeholder"
placeholder={this._placeholderName}
blurToCancel={false}
initialValue={this.state.name}
onValueChanged={this._onValueChanged}
dir="auto" />
</div>
);
},
});

View file

@ -517,15 +517,13 @@ export default class RoomSublist extends React.Component<IProps, IState> {
if (this.state.rooms) { if (this.state.rooms) {
const visibleRooms = this.state.rooms.slice(0, this.numVisibleTiles); const visibleRooms = this.state.rooms.slice(0, this.numVisibleTiles);
for (const room of visibleRooms) { for (const room of visibleRooms) {
tiles.push( tiles.push(<RoomTile
<RoomTile room={room}
room={room} key={`room-${room.roomId}`}
key={`room-${room.roomId}`} showMessagePreview={this.layout.showPreviews}
showMessagePreview={this.layout.showPreviews} isMinimized={this.props.isMinimized}
isMinimized={this.props.isMinimized} tag={this.props.tagId}
tag={this.props.tagId} />);
/>
);
} }
} }
@ -710,7 +708,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
// doesn't become sticky. // doesn't become sticky.
// The same applies to the notification badge. // The same applies to the notification badge.
return ( return (
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}> <div
className={classes}
onKeyDown={this.onHeaderKeyDown}
onFocus={onFocus}
aria-label={this.props.label}
>
<div className="mx_RoomSublist_stickable"> <div className="mx_RoomSublist_stickable">
<Button <Button
onFocus={onFocus} onFocus={onFocus}
@ -762,7 +765,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
const showMoreAtMinHeight = minTiles < this.numTiles; const showMoreAtMinHeight = minTiles < this.numTiles;
const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0); const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0);
const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding); const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding);
let maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding); const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
const showMoreBtnClasses = classNames({ const showMoreBtnClasses = classNames({
'mx_RoomSublist_showNButton': true, 'mx_RoomSublist_showNButton': true,
}); });

View file

@ -31,7 +31,7 @@ import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextM
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore"; import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE, } from "../../../RoomNotifs"; import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import { Volume } from "../../../RoomNotifsTypes"; import { Volume } from "../../../RoomNotifsTypes";
@ -48,7 +48,7 @@ import IconizedContextMenu, {
IconizedContextMenuCheckbox, IconizedContextMenuCheckbox,
IconizedContextMenuOption, IconizedContextMenuOption,
IconizedContextMenuOptionList, IconizedContextMenuOptionList,
IconizedContextMenuRadio IconizedContextMenuRadio,
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore"; import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
@ -249,7 +249,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
removeTag, removeTag,
addTag, addTag,
undefined, undefined,
0 0,
)); ));
} else { } else {
console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`); console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);

View file

@ -1,68 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from "../../../languageHandler";
export default createReactClass({
displayName: 'RoomTopicEditor',
propTypes: {
room: PropTypes.object.isRequired,
},
getInitialState: function() {
return {
topic: null,
};
},
componentDidMount: function() {
const room = this.props.room;
const topic = room.currentState.getStateEvents('m.room.topic', '');
this.setState({
topic: topic ? topic.getContent().topic : '',
});
},
getTopic: function() {
return this.state.topic;
},
_onValueChanged: function(value) {
this.setState({
topic: value,
});
},
render: function() {
const EditableText = sdk.getComponent("elements.EditableText");
return (
<EditableText
className="mx_RoomHeader_topic mx_RoomHeader_editable"
placeholderClassName="mx_RoomHeader_placeholder"
placeholder={_t("Add a topic")}
blurToCancel={false}
initialValue={this.state.topic}
onValueChanged={this._onValueChanged}
dir="auto" />
);
},
});

View file

@ -19,9 +19,7 @@ import classNames from "classnames";
import { import {
RovingAccessibleButton, RovingAccessibleButton,
RovingAccessibleTooltipButton, RovingAccessibleTooltipButton,
RovingTabIndexWrapper
} from "../../../accessibility/RovingTabIndex"; } from "../../../accessibility/RovingTabIndex";
import AccessibleButton from "../../views/elements/AccessibleButton";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import { NotificationState } from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";

View file

@ -42,7 +42,7 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string) {
return _t('Downloading update...'); return _t('Downloading update...');
case UpdateCheckStatus.Ready: case UpdateCheckStatus.Ready:
return _t("New version available. <a>Update now.</a>", {}, { return _t("New version available. <a>Update now.</a>", {}, {
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton> a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>,
}); });
} }
} }

View file

@ -170,7 +170,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
"baseFontSize", "baseFontSize",
null, null,
SettingLevel.DEVICE, SettingLevel.DEVICE,
parseInt(value, 10) - FontWatcher.SIZE_DIFF parseInt(value, 10) - FontWatcher.SIZE_DIFF,
); );
return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})}; return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})};
@ -294,7 +294,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
/> />
</div> </div>
{customThemeForm} {customThemeForm}
</div> </div>
); );
} }

View file

@ -29,7 +29,15 @@ interface IProps extends IGenericToastProps {
const SECOND = 1000; const SECOND = 1000;
const GenericExpiringToast: React.FC<IProps> = ({description, acceptLabel, dismissLabel, onAccept, onDismiss, toastKey, numSeconds}) => { const GenericExpiringToast: React.FC<IProps> = ({
description,
acceptLabel,
dismissLabel,
onAccept,
onDismiss,
toastKey,
numSeconds,
}) => {
const onReject = () => { const onReject = () => {
if (onDismiss) onDismiss(); if (onDismiss) onDismiss();
ToastStore.sharedInstance().dismissToast(toastKey); ToastStore.sharedInstance().dismissToast(toastKey);

View file

@ -31,7 +31,13 @@ interface IPropsExtended extends IProps {
onReject(); onReject();
} }
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => { const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({
description,
acceptLabel,
rejectLabel,
onAccept,
onReject,
}) => {
return <div> return <div>
<div className="mx_Toast_description"> <div className="mx_Toast_description">
{ description } { description }

View file

@ -97,10 +97,7 @@ export default class CallView extends React.Component<IProps, IState> {
if (this.props.room) { if (this.props.room) {
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
call = CallHandler.getCallForRoom(roomId) || call = CallHandler.getCallForRoom(roomId) ||
(this.props.ConferenceHandler ? (this.props.ConferenceHandler ? this.props.ConferenceHandler.getConferenceCallForRoom(roomId) : null);
this.props.ConferenceHandler.getConferenceCallForRoom(roomId) :
null
);
if (this.call) { if (this.call) {
this.setState({ call: call }); this.setState({ call: call });

View file

@ -51,7 +51,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
private onAction = (payload: ActionPayload) => { private onAction = (payload: ActionPayload) => {
switch (payload.action) { switch (payload.action) {
case 'call_state': case 'call_state': {
const call = CallHandler.getCall(payload.room_id); const call = CallHandler.getCall(payload.room_id);
if (call && call.call_state === 'ringing') { if (call && call.call_state === 'ringing') {
this.setState({ this.setState({
@ -62,6 +62,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
incomingCall: null, incomingCall: null,
}); });
} }
}
} }
}; };

View file

@ -1200,7 +1200,6 @@
"%(count)s unread messages.|other": "%(count)s unread messages.", "%(count)s unread messages.|other": "%(count)s unread messages.",
"%(count)s unread messages.|one": "1 unread message.", "%(count)s unread messages.|one": "1 unread message.",
"Unread messages.": "Unread messages.", "Unread messages.": "Unread messages.",
"Add a topic": "Add a topic",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
"This room has already been upgraded.": "This room has already been upgraded.", "This room has already been upgraded.": "This room has already been upgraded.",
"This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.", "This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.",
@ -1556,8 +1555,6 @@
"Room directory": "Room directory", "Room directory": "Room directory",
"Sign in with single sign-on": "Sign in with single sign-on", "Sign in with single sign-on": "Sign in with single sign-on",
"And %(count)s more...|other": "And %(count)s more...", "And %(count)s more...|other": "And %(count)s more...",
"ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User",
"Home": "Home", "Home": "Home",
"Enter a server name": "Enter a server name", "Enter a server name": "Enter a server name",
"Looks good": "Looks good", "Looks good": "Looks good",
@ -1888,10 +1885,6 @@
"<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.", "<b>Warning</b>: You should only set up key backup from a trusted computer.": "<b>Warning</b>: You should only set up key backup from a trusted computer.",
"Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.", "Access your secure message history and set up secure messaging by entering your recovery key.": "Access your secure message history and set up secure messaging by entering your recovery key.",
"If you've forgotten your recovery key you can <button>set up new recovery options</button>": "If you've forgotten your recovery key you can <button>set up new recovery options</button>", "If you've forgotten your recovery key you can <button>set up new recovery options</button>": "If you've forgotten your recovery key you can <button>set up new recovery options</button>",
"Private Chat": "Private Chat",
"Public Chat": "Public Chat",
"Custom": "Custom",
"Address (optional)": "Address (optional)",
"Reject invitation": "Reject invitation", "Reject invitation": "Reject invitation",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Unable to reject invite": "Unable to reject invite", "Unable to reject invite": "Unable to reject invite",
@ -1985,11 +1978,6 @@
"Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s",
"Sign in to your Matrix account on <underlinedServerName />": "Sign in to your Matrix account on <underlinedServerName />", "Sign in to your Matrix account on <underlinedServerName />": "Sign in to your Matrix account on <underlinedServerName />",
"Sign in with SSO": "Sign in with SSO", "Sign in with SSO": "Sign in with SSO",
"Sorry, your browser is <b>not</b> able to run %(brand)s.": "Sorry, your browser is <b>not</b> able to run %(brand)s.",
"%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.": "%(brand)s uses many advanced browser features, some of which are not available or experimental in your current browser.",
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!",
"I understand the risks and wish to continue": "I understand the risks and wish to continue",
"Couldn't load page": "Couldn't load page", "Couldn't load page": "Couldn't load page",
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality", "You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files", "You must join the room to see its files": "You must join the room to see its files",

View file

@ -442,7 +442,7 @@ export function pickBestLanguage(langs: string[]): string {
} }
function getLangsJson(): Promise<object> { function getLangsJson(): Promise<object> {
return new Promise(async (resolve, reject) => { return new Promise((resolve, reject) => {
let url; let url;
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
url = webpackLangJsonUrl; url = webpackLangJsonUrl;
@ -453,7 +453,7 @@ function getLangsJson(): Promise<object> {
{ method: "GET", url }, { method: "GET", url },
(err, response, body) => { (err, response, body) => {
if (err || response.status < 200 || response.status >= 300) { if (err || response.status < 200 || response.status >= 300) {
reject({err: err, response: response}); reject(err);
return; return;
} }
resolve(JSON.parse(body)); resolve(JSON.parse(body));
@ -488,7 +488,7 @@ function getLanguage(langPath: string): object {
{ method: "GET", url: langPath }, { method: "GET", url: langPath },
(err, response, body) => { (err, response, body) => {
if (err || response.status < 200 || response.status >= 300) { if (err || response.status < 200 || response.status >= 300) {
reject({err: err, response: response}); reject(err);
return; return;
} }
resolve(weblateToCounterpart(JSON.parse(body))); resolve(weblateToCounterpart(JSON.parse(body)));

934
yarn.lock

File diff suppressed because it is too large Load diff