Merge remote-tracking branch 'origin/release-v3.43.0'
# Conflicts: # CHANGELOG.md # package.json # src/components/views/messages/MessageActionBar.tsx
This commit is contained in:
commit
6b0156d876
680 changed files with 12433 additions and 5108 deletions
|
@ -22,7 +22,6 @@ import classNames from "classnames";
|
|||
import FocusLock from "react-focus-lock";
|
||||
|
||||
import { Writeable } from "../../@types/common";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import { checkInputableElement, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
@ -105,7 +104,6 @@ interface IState {
|
|||
// Generic ContextMenu Portal wrapper
|
||||
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
||||
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
||||
@replaceableComponent("structures.ContextMenu")
|
||||
export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||
private readonly initialFocus: HTMLElement;
|
||||
|
||||
|
@ -431,7 +429,7 @@ export type AboveLeftOf = IPosition & {
|
|||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect,
|
||||
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?)
|
||||
export const aboveLeftOf = (
|
||||
elementRect: DOMRect,
|
||||
elementRect: Pick<DOMRect, "right" | "top" | "bottom">,
|
||||
chevronFace = ChevronFace.None,
|
||||
vPadding = 0,
|
||||
): AboveLeftOf => {
|
||||
|
@ -452,9 +450,37 @@ export const aboveLeftOf = (
|
|||
return menuOptions;
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the right of elementRect,
|
||||
// and either above or below: wherever there is more space (maybe this should be aboveOrBelowRightOf?)
|
||||
export const aboveRightOf = (
|
||||
elementRect: Pick<DOMRect, "left" | "top" | "bottom">,
|
||||
chevronFace = ChevronFace.None,
|
||||
vPadding = 0,
|
||||
): AboveLeftOf => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
const buttonLeft = elementRect.left + window.pageXOffset;
|
||||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
||||
const buttonTop = elementRect.top + window.pageYOffset;
|
||||
// Align the left edge of the menu to the left edge of the button
|
||||
menuOptions.left = buttonLeft;
|
||||
// Align the menu vertically on whichever side of the button has more space available.
|
||||
if (buttonBottom < UIStore.instance.windowHeight / 2) {
|
||||
menuOptions.top = buttonBottom + vPadding;
|
||||
} else {
|
||||
menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding;
|
||||
}
|
||||
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
||||
// and always above elementRect
|
||||
export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
|
||||
export const alwaysAboveLeftOf = (
|
||||
elementRect: Pick<DOMRect, "right" | "bottom" | "top">,
|
||||
chevronFace = ChevronFace.None,
|
||||
vPadding = 0,
|
||||
) => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
const buttonRight = elementRect.right + window.pageXOffset;
|
||||
|
@ -474,7 +500,11 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac
|
|||
|
||||
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the right of elementRect
|
||||
// and always above elementRect
|
||||
export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => {
|
||||
export const alwaysAboveRightOf = (
|
||||
elementRect: Pick<DOMRect, "left" | "top">,
|
||||
chevronFace = ChevronFace.None,
|
||||
vPadding = 0,
|
||||
) => {
|
||||
const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace };
|
||||
|
||||
const buttonLeft = elementRect.left + window.pageXOffset;
|
||||
|
|
|
@ -37,7 +37,7 @@ interface IProps {
|
|||
// Whether to wrap the page in a scrollbar
|
||||
scrollbar?: boolean;
|
||||
// Map of keys to replace with values, e.g {$placeholder: "value"}
|
||||
replaceMap?: Map<string, string>;
|
||||
replaceMap?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -57,8 +57,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
|||
};
|
||||
}
|
||||
|
||||
protected translate(s: string): string {
|
||||
// default implementation - skins may wish to extend this
|
||||
private translate(s: string): string {
|
||||
return sanitizeHtml(_t(s));
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ import EventIndexPeg from "../../indexing/EventIndexPeg";
|
|||
import { _t } from '../../languageHandler';
|
||||
import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuildsNotice";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
|
@ -51,7 +50,6 @@ interface IState {
|
|||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
*/
|
||||
@replaceableComponent("structures.FilePanel")
|
||||
class FilePanel extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
|
||||
|
|
|
@ -16,14 +16,11 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
title: React.ReactNode;
|
||||
message: React.ReactNode;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.GenericErrorPage")
|
||||
export default class GenericErrorPage extends React.PureComponent<IProps> {
|
||||
render() {
|
||||
return <div className='mx_GenericErrorPage'>
|
||||
|
|
|
@ -21,7 +21,6 @@ import AutoHideScrollbar from './AutoHideScrollbar';
|
|||
import { getHomePageUrl } from "../../utils/pages";
|
||||
import { _tDom } from "../../languageHandler";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import * as sdk from "../../index";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||
|
@ -33,6 +32,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import MiniAvatarUploader, { AVATAR_SIZE } from "../views/elements/MiniAvatarUploader";
|
||||
import Analytics from "../../Analytics";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import EmbeddedPage from "./EmbeddedPage";
|
||||
|
||||
const onClickSendDm = () => {
|
||||
Analytics.trackEvent('home_page', 'button', 'dm');
|
||||
|
@ -94,8 +94,6 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
|
|||
const pageUrl = getHomePageUrl(config);
|
||||
|
||||
if (pageUrl) {
|
||||
// FIXME: Using an import will result in wrench-element-tests failures
|
||||
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
||||
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
import { _t } from "../../languageHandler";
|
||||
import { HostSignupStore } from "../../stores/HostSignupStore";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
onClick?(): void;
|
||||
|
@ -31,7 +30,6 @@ interface IProps {
|
|||
|
||||
interface IState {}
|
||||
|
||||
@replaceableComponent("structures.HostSignupAction")
|
||||
export default class HostSignupAction extends React.PureComponent<IProps, IState> {
|
||||
private openDialog = async () => {
|
||||
this.props.onClick?.();
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ComponentProps, createRef } from "react";
|
||||
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
|
||||
|
||||
interface IProps extends Omit<ComponentProps<typeof AutoHideScrollbar>, "onWheel"> {
|
||||
|
@ -40,7 +39,6 @@ interface IState {
|
|||
rightIndicatorOffset: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.IndicatorScrollbar")
|
||||
export default class IndicatorScrollbar extends React.Component<IProps, IState> {
|
||||
private autoHideScrollbar = createRef<AutoHideScrollbar>();
|
||||
private scrollElement: HTMLDivElement;
|
||||
|
|
|
@ -28,7 +28,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import getEntryComponentForLoginType, { IStageComponent } from '../views/auth/InteractiveAuthEntryComponents';
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||
|
||||
|
@ -89,7 +88,6 @@ interface IState {
|
|||
submitButtonEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.InteractiveAuthComponent")
|
||||
export default class InteractiveAuthComponent extends React.Component<IProps, IState> {
|
||||
private readonly authLogic: InteractiveAuth;
|
||||
private readonly intervalId: number = null;
|
||||
|
|
|
@ -27,7 +27,6 @@ import { Action } from "../../dispatcher/actions";
|
|||
import RoomSearch from "./RoomSearch";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||
import { MetaSpace, SpaceKey, UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
|
@ -61,7 +60,6 @@ interface IState {
|
|||
activeSpace: SpaceKey;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.LeftPanel")
|
||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private listContainerRef = createRef<HTMLDivElement>();
|
||||
private roomSearchRef = createRef<RoomSearch>();
|
||||
|
|
|
@ -52,7 +52,6 @@ import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
|||
import { getKeyBindingsManager } from '../../KeyBindingsManager';
|
||||
import { IOpts } from "../../createRoom";
|
||||
import SpacePanel from "../views/spaces/SpacePanel";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||
import { OwnProfileStore } from '../../stores/OwnProfileStore';
|
||||
|
@ -63,7 +62,7 @@ import ToastContainer from './ToastContainer';
|
|||
import UserView from "./UserView";
|
||||
import BackdropPanel from "./BackdropPanel";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||
import { UserTab } from "../views/dialogs/UserTab";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
import RightPanelStore from '../../stores/right-panel/RightPanelStore';
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
|
@ -127,7 +126,6 @@ interface IState {
|
|||
*
|
||||
* Components mounted below us can access the matrix client via the react context.
|
||||
*/
|
||||
@replaceableComponent("structures.LoggedInView")
|
||||
class LoggedInView extends React.Component<IProps, IState> {
|
||||
static displayName = 'LoggedInView';
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { NumberSize, Resizable } from 're-resizable';
|
||||
import { Direction } from "re-resizable/lib/resizer";
|
||||
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
|
||||
interface IProps {
|
||||
|
@ -28,7 +27,6 @@ interface IProps {
|
|||
panel?: JSX.Element;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.MainSplit")
|
||||
export default class MainSplit extends React.Component<IProps> {
|
||||
private onResizeStart = (): void => {
|
||||
this.props.resizeNotifier.startResizing();
|
||||
|
|
|
@ -33,7 +33,7 @@ import { throttle } from "lodash";
|
|||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import { RoomType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by various components
|
||||
import 'focus-visible';
|
||||
// what-input helps improve keyboard accessibility
|
||||
import 'what-input';
|
||||
|
@ -84,19 +84,18 @@ import {
|
|||
UPDATE_STATUS_INDICATOR,
|
||||
} from "../../stores/notifications/RoomNotificationStateStore";
|
||||
import { SettingLevel } from "../../settings/SettingLevel";
|
||||
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
|
||||
import { UIFeature } from "../../settings/UIFeature";
|
||||
import DialPadModal from "../views/voip/DialPadModal";
|
||||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import { RoomUpdateCause } from "../../stores/room-list/models";
|
||||
import SecurityCustomisations from "../../customisations/Security";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||
import UserSettingsDialog, { UserTab } from '../views/dialogs/UserSettingsDialog';
|
||||
import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
|
||||
import { UserTab } from "../views/dialogs/UserTab";
|
||||
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
|
||||
import RoomDirectory from './RoomDirectory';
|
||||
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
||||
|
@ -131,6 +130,7 @@ import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStart
|
|||
import { IConfigOptions } from "../../IConfigOptions";
|
||||
import { SnakedObject } from "../../utils/SnakedObject";
|
||||
import InfoDialog from '../views/dialogs/InfoDialog';
|
||||
import { leaveRoomBehaviour } from "../../utils/leave-behaviour";
|
||||
|
||||
// legacy export
|
||||
export { default as Views } from "../../Views";
|
||||
|
@ -208,7 +208,6 @@ interface IState {
|
|||
forceTimeline?: boolean; // see props
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.MatrixChat")
|
||||
export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
static displayName = "MatrixChat";
|
||||
|
||||
|
@ -2097,12 +2096,3 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
</ErrorBoundary>;
|
||||
}
|
||||
}
|
||||
|
||||
export function isLoggedIn(): boolean {
|
||||
// JRS: Maybe we should move the step that writes this to the window out of
|
||||
// `element-web` and into this file? Better yet, we should probably create a
|
||||
// store to hold this state.
|
||||
// See also https://github.com/vector-im/element-web/issues/15034.
|
||||
const app = window.matrixChat;
|
||||
return app && (app as MatrixChat).state.view === Views.LOGGED_IN;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent, ReactNode, SyntheticEvent, TransitionEvent } from 'react';
|
||||
import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
|
@ -31,13 +31,12 @@ import SettingsStore from '../../settings/SettingsStore';
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import { _t } from "../../languageHandler";
|
||||
import EventTile, { UnwrappedEventTile, haveTileForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
|
||||
import EventTile, { UnwrappedEventTile, IReadReceiptProps } from "../views/rooms/EventTile";
|
||||
import { hasText } from "../../TextForEvent";
|
||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
import DMRoomMap from "../../utils/DMRoomMap";
|
||||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||
import HistoryTile from "../views/rooms/HistoryTile";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import defaultDispatcher from '../../dispatcher/dispatcher';
|
||||
import CallEventGrouper from "./CallEventGrouper";
|
||||
import WhoIsTypingTile from '../views/rooms/WhoIsTypingTile';
|
||||
|
@ -51,8 +50,9 @@ import Spinner from "../views/elements/Spinner";
|
|||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||
import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
||||
import { Action } from '../../dispatcher/actions';
|
||||
import { getEventDisplayInfo } from "../../utils/EventUtils";
|
||||
import { getEventDisplayInfo } from "../../utils/EventRenderingUtils";
|
||||
import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
import { editorRoomKey } from "../../Editing";
|
||||
|
||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
|
@ -97,7 +97,7 @@ export function shouldFormContinuation(
|
|||
timelineRenderingType !== TimelineRenderingType.Thread) return false;
|
||||
|
||||
// if we don't have tile for previous event then it was shown by showHiddenEvents and has no SenderProfile
|
||||
if (!haveTileForEvent(prevEvent, showHiddenEvents)) return false;
|
||||
if (!haveRendererForEvent(prevEvent, showHiddenEvents)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -170,9 +170,6 @@ interface IProps {
|
|||
// callback which is called when the panel is scrolled.
|
||||
onScroll?(event: Event): void;
|
||||
|
||||
// callback which is called when the user interacts with the room timeline
|
||||
onUserScroll(event: SyntheticEvent): void;
|
||||
|
||||
// callback which is called when more content is needed.
|
||||
onFillRequest?(backwards: boolean): Promise<boolean>;
|
||||
|
||||
|
@ -200,7 +197,6 @@ interface IReadReceiptForUser {
|
|||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
@replaceableComponent("structures.MessagePanel")
|
||||
export default class MessagePanel extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
@ -246,7 +242,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// displayed event in the current render cycle.
|
||||
private readReceiptsByUserId: Record<string, IReadReceiptForUser> = {};
|
||||
|
||||
private readonly showHiddenEventsInTimeline: boolean;
|
||||
private readonly _showHiddenEvents: boolean;
|
||||
private readonly threadsEnabled: boolean;
|
||||
private isMounted = false;
|
||||
|
||||
|
@ -274,7 +270,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// Cache these settings on mount since Settings is expensive to query,
|
||||
// and we check this in a hot code path. This is also cached in our
|
||||
// RoomContext, however we still need a fallback for roomless MessagePanels.
|
||||
this.showHiddenEventsInTimeline = SettingsStore.getValue("showHiddenEventsInTimeline");
|
||||
this._showHiddenEvents = SettingsStore.getValue("showHiddenEventsInTimeline");
|
||||
this.threadsEnabled = SettingsStore.getValue("feature_thread");
|
||||
|
||||
this.showTypingNotificationsWatcherRef =
|
||||
|
@ -469,7 +465,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
public get showHiddenEvents(): boolean {
|
||||
return this.context?.showHiddenEventsInTimeline ?? this.showHiddenEventsInTimeline;
|
||||
return this.context?.showHiddenEvents ?? this._showHiddenEvents;
|
||||
}
|
||||
|
||||
// TODO: Implement granular (per-room) hide options
|
||||
|
@ -492,7 +488,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!haveTileForEvent(mxEv, this.showHiddenEvents)) {
|
||||
if (!haveRendererForEvent(mxEv, this.showHiddenEvents)) {
|
||||
return false; // no tile = no show
|
||||
}
|
||||
|
||||
|
@ -752,7 +748,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
const willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEv.getDate() || new Date());
|
||||
lastInSection = willWantDateSeparator ||
|
||||
mxEv.getSender() !== nextEv.getSender() ||
|
||||
getEventDisplayInfo(nextEv).isInfoMessage ||
|
||||
getEventDisplayInfo(nextEv, this.showHiddenEvents).isInfoMessage ||
|
||||
!shouldFormContinuation(
|
||||
mxEv, nextEv, this.showHiddenEvents, this.threadsEnabled, this.context.timelineRenderingType,
|
||||
);
|
||||
|
@ -1031,7 +1027,6 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
ref={this.scrollPanel}
|
||||
className={classes}
|
||||
onScroll={this.props.onScroll}
|
||||
onUserScroll={this.props.onUserScroll}
|
||||
onFillRequest={this.props.onFillRequest}
|
||||
onUnfillRequest={this.props.onUnfillRequest}
|
||||
style={style}
|
||||
|
|
|
@ -19,7 +19,6 @@ import * as React from "react";
|
|||
import { ComponentClass } from "../../@types/common";
|
||||
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
@ -28,7 +27,6 @@ interface IState {
|
|||
toasts: ComponentClass[];
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.NonUrgentToastContainer")
|
||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
|
|
|
@ -20,7 +20,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { _t } from '../../languageHandler';
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import TimelinePanel from "./TimelinePanel";
|
||||
import Spinner from "../views/elements/Spinner";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
|
@ -38,7 +37,6 @@ interface IState {
|
|||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
@replaceableComponent("structures.NotificationPanel")
|
||||
export default class NotificationPanel extends React.PureComponent<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
|||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import MemberList from "../views/rooms/MemberList";
|
||||
import UserInfo from "../views/right_panel/UserInfo";
|
||||
|
@ -60,7 +59,6 @@ interface IState {
|
|||
cardState?: IRightPanelCardState;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RightPanel")
|
||||
export default class RightPanel extends React.Component<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
@ -233,6 +231,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
mxEvent={cardState.threadHeadEvent}
|
||||
initialEvent={cardState.initialEvent}
|
||||
isInitialEventHighlighted={cardState.isInitialEventHighlighted}
|
||||
initialEventScrollIntoView={cardState.initialEventScrollIntoView}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
e2eStatus={this.props.e2eStatus}
|
||||
/>;
|
||||
|
|
|
@ -24,18 +24,14 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import Modal from "../../Modal";
|
||||
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
||||
import { _t } from '../../languageHandler';
|
||||
import SdkConfig from '../../SdkConfig';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
import NetworkDropdown, { ALL_ROOMS, Protocols } from "../views/directory/NetworkDropdown";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../customisations/Media";
|
||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||
|
@ -46,9 +42,7 @@ import { getDisplayAliasForAliasSet } from "../../Rooms";
|
|||
import { Action } from "../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 800;
|
||||
import { PublicRoomTile } from "../views/rooms/PublicRoomTile";
|
||||
|
||||
const LAST_SERVER_KEY = "mx_last_room_directory_server";
|
||||
const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
|
||||
|
@ -71,7 +65,6 @@ interface IState {
|
|||
filterString: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomDirectory")
|
||||
export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||
private unmounted = false;
|
||||
private nextBatch: string = null;
|
||||
|
@ -251,7 +244,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
* HS admins to do this through the RoomSettings interface, but
|
||||
* this needs SPEC-417.
|
||||
*/
|
||||
private removeFromDirectory(room: IPublicRoomsChunkRoom) {
|
||||
private removeFromDirectory = (room: IPublicRoomsChunkRoom) => {
|
||||
const alias = getDisplayAliasForRoom(room);
|
||||
const name = room.name || alias || _t('Unnamed room');
|
||||
|
||||
|
@ -291,14 +284,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: React.MouseEvent) => {
|
||||
// If room was shift-clicked, remove it from the room directory
|
||||
if (ev.shiftKey) {
|
||||
ev.preventDefault();
|
||||
this.removeFromDirectory(room);
|
||||
}
|
||||
};
|
||||
|
||||
private onOptionChange = (server: string, instanceId?: string) => {
|
||||
|
@ -406,21 +391,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onPreviewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
|
||||
this.showRoom(room, null, false, true);
|
||||
ev.stopPropagation();
|
||||
};
|
||||
|
||||
private onViewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
|
||||
this.showRoom(room);
|
||||
ev.stopPropagation();
|
||||
};
|
||||
|
||||
private onJoinClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
|
||||
this.showRoom(room, null, true);
|
||||
ev.stopPropagation();
|
||||
};
|
||||
|
||||
private onCreateRoomClick = (ev: ButtonEvent) => {
|
||||
this.onFinished();
|
||||
dis.dispatch({
|
||||
|
@ -435,7 +405,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
this.showRoom(null, alias, autoJoin);
|
||||
}
|
||||
|
||||
private showRoom(room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) {
|
||||
private showRoom = (room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) => {
|
||||
this.onFinished();
|
||||
const payload: ViewRoomPayload = {
|
||||
action: Action.ViewRoom,
|
||||
|
@ -479,112 +449,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
payload.room_id = room.room_id;
|
||||
}
|
||||
dis.dispatch(payload);
|
||||
}
|
||||
|
||||
private createRoomCells(room: IPublicRoomsChunkRoom) {
|
||||
const client = MatrixClientPeg.get();
|
||||
const clientRoom = client.getRoom(room.room_id);
|
||||
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
|
||||
const isGuest = client.isGuest();
|
||||
let previewButton;
|
||||
let joinOrViewButton;
|
||||
|
||||
// Element Web currently does not allow guests to join rooms, so we
|
||||
// instead show them preview buttons for all rooms. If the room is not
|
||||
// world readable, a modal will appear asking you to register first. If
|
||||
// it is readable, the preview appears as normal.
|
||||
if (!hasJoinedRoom && (room.world_readable || isGuest)) {
|
||||
previewButton = (
|
||||
<AccessibleButton kind="secondary" onClick={(ev) => this.onPreviewClick(ev, room)}>
|
||||
{ _t("Preview") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
if (hasJoinedRoom) {
|
||||
joinOrViewButton = (
|
||||
<AccessibleButton kind="secondary" onClick={(ev) => this.onViewClick(ev, room)}>
|
||||
{ _t("View") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
} else if (!isGuest) {
|
||||
joinOrViewButton = (
|
||||
<AccessibleButton kind="primary" onClick={(ev) => this.onJoinClick(ev, room)}>
|
||||
{ _t("Join") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
let name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
|
||||
if (name.length > MAX_NAME_LENGTH) {
|
||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||
}
|
||||
|
||||
let topic = room.topic || '';
|
||||
// Additional truncation based on line numbers is done via CSS,
|
||||
// but to ensure that the DOM is not polluted with a huge string
|
||||
// we give it a hard limit before rendering.
|
||||
if (topic.length > MAX_TOPIC_LENGTH) {
|
||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyAndSanitizeHtml(topic);
|
||||
let avatarUrl = null;
|
||||
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
|
||||
|
||||
// We use onMouseDown instead of onClick, so that we can avoid text getting selected
|
||||
return <div
|
||||
key={room.room_id}
|
||||
role="listitem"
|
||||
className="mx_RoomDirectory_listItem"
|
||||
>
|
||||
<div
|
||||
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||
className="mx_RoomDirectory_roomAvatar"
|
||||
>
|
||||
<BaseAvatar
|
||||
width={32}
|
||||
height={32}
|
||||
resizeMethod='crop'
|
||||
name={name}
|
||||
idName={name}
|
||||
url={avatarUrl}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||
className="mx_RoomDirectory_roomDescription"
|
||||
>
|
||||
<div className="mx_RoomDirectory_name">
|
||||
{ name }
|
||||
</div>
|
||||
<div
|
||||
className="mx_RoomDirectory_topic"
|
||||
dangerouslySetInnerHTML={{ __html: topic }}
|
||||
/>
|
||||
<div className="mx_RoomDirectory_alias">
|
||||
{ getDisplayAliasForRoom(room) }
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||
className="mx_RoomDirectory_roomMemberCount"
|
||||
>
|
||||
{ room.num_joined_members }
|
||||
</div>
|
||||
<div
|
||||
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||
className="mx_RoomDirectory_preview"
|
||||
>
|
||||
{ previewButton }
|
||||
</div>
|
||||
<div
|
||||
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
|
||||
className="mx_RoomDirectory_join"
|
||||
>
|
||||
{ joinOrViewButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
};
|
||||
private stringLooksLikeId(s: string, fieldType: IFieldType) {
|
||||
let pat = /^#[^\s]+:[^\s]/;
|
||||
if (fieldType && fieldType.regexp) {
|
||||
|
@ -622,7 +487,14 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
content = <Spinner />;
|
||||
} else {
|
||||
const cells = (this.state.publicRooms || [])
|
||||
.reduce((cells, room) => cells.concat(this.createRoomCells(room)), []);
|
||||
.map(room =>
|
||||
<PublicRoomTile
|
||||
key={room.room_id}
|
||||
room={room}
|
||||
showRoom={this.showRoom}
|
||||
removeFromDirectory={this.removeFromDirectory}
|
||||
/>,
|
||||
);
|
||||
// we still show the scrollpanel, at least for now, because
|
||||
// otherwise we don't fetch more because we don't get a fill
|
||||
// request from the scrollpanel because there isn't one
|
||||
|
|
|
@ -26,7 +26,6 @@ import { Action } from "../../dispatcher/actions";
|
|||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
||||
import { isMac, Key } from "../../Keyboard";
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
spotlightBetaEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomSearch")
|
||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly betaRef: string;
|
||||
|
|
|
@ -24,7 +24,6 @@ import Resend from '../../Resend';
|
|||
import dis from '../../dispatcher/dispatcher';
|
||||
import { messageForResourceLimitError } from '../../utils/ErrorUtils';
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import NotificationBadge from "../views/rooms/NotificationBadge";
|
||||
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
|
@ -82,7 +81,6 @@ interface IState {
|
|||
isResending: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomStatusBar")
|
||||
export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
||||
private unmounted = false;
|
||||
public static contextType = MatrixClientContext;
|
||||
|
|
|
@ -23,7 +23,7 @@ limitations under the License.
|
|||
import React, { createRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { IRecommendedVersion, NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { IThreadBundledRelationship, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventSubscription } from "fbemitter";
|
||||
import { ISearchResults } from 'matrix-js-sdk/src/@types/search';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
@ -35,6 +35,7 @@ import { throttle } from "lodash";
|
|||
import { MatrixError } from 'matrix-js-sdk/src/http-api';
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
import { THREAD_RELATION_TYPE } from 'matrix-js-sdk/src/models/thread';
|
||||
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { _t } from '../../languageHandler';
|
||||
|
@ -48,14 +49,13 @@ import * as Rooms from '../../Rooms';
|
|||
import eventSearch, { searchPagination } from '../../Searching';
|
||||
import MainSplit from './MainSplit';
|
||||
import RightPanel from './RightPanel';
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
import { RoomViewStore } from '../../stores/RoomViewStore';
|
||||
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
|
||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import MatrixClientContext, { MatrixClientProps, withMatrixClientHOC } from "../../contexts/MatrixClientContext";
|
||||
import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils';
|
||||
|
@ -86,7 +86,6 @@ import { getKeyBindingsManager } from '../../KeyBindingsManager';
|
|||
import { objectHasDiff } from "../../utils/objects";
|
||||
import SpaceRoomView from "./SpaceRoomView";
|
||||
import { IOpts } from "../../createRoom";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
||||
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||
import SearchResultTile from '../views/rooms/SearchResultTile';
|
||||
|
@ -109,6 +108,7 @@ import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyn
|
|||
import FileDropTarget from './FileDropTarget';
|
||||
import Measured from '../views/elements/Measured';
|
||||
import { FocusComposerPayload } from '../../dispatcher/payloads/FocusComposerPayload';
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -156,6 +156,8 @@ export interface IRoomState {
|
|||
initialEventPixelOffset?: number;
|
||||
// Whether to highlight the event scrolled to
|
||||
isInitialEventHighlighted?: boolean;
|
||||
// Whether to scroll the event into view
|
||||
initialEventScrollIntoView?: boolean;
|
||||
replyToEvent?: MatrixEvent;
|
||||
numUnreadMessages: number;
|
||||
searchTerm?: string;
|
||||
|
@ -197,7 +199,7 @@ export interface IRoomState {
|
|||
showTwelveHourTimestamps: boolean;
|
||||
readMarkerInViewThresholdMs: number;
|
||||
readMarkerOutOfViewThresholdMs: number;
|
||||
showHiddenEventsInTimeline: boolean;
|
||||
showHiddenEvents: boolean;
|
||||
showReadReceipts: boolean;
|
||||
showRedactions: boolean;
|
||||
showJoinLeaves: boolean;
|
||||
|
@ -220,7 +222,6 @@ export interface IRoomState {
|
|||
narrow: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomView")
|
||||
export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly roomStoreToken: EventSubscription;
|
||||
|
@ -270,7 +271,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
showTwelveHourTimestamps: SettingsStore.getValue("showTwelveHourTimestamps"),
|
||||
readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"),
|
||||
readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"),
|
||||
showHiddenEventsInTimeline: SettingsStore.getValue("showHiddenEventsInTimeline"),
|
||||
showHiddenEvents: SettingsStore.getValue("showHiddenEventsInTimeline"),
|
||||
showReadReceipts: true,
|
||||
showRedactions: true,
|
||||
showJoinLeaves: true,
|
||||
|
@ -298,7 +299,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
context.on(CryptoEvent.KeysChanged, this.onCrossSigningKeysChanged);
|
||||
context.on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
// Start listening for RoomViewStore updates
|
||||
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||
this.roomStoreToken = RoomViewStore.instance.addListener(this.onRoomViewStoreUpdate);
|
||||
|
||||
RightPanelStore.instance.on(UPDATE_EVENT, this.onRightPanelStoreUpdate);
|
||||
|
||||
|
@ -327,7 +328,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.setState({ readMarkerOutOfViewThresholdMs: value as number }),
|
||||
),
|
||||
SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[,,, value]) =>
|
||||
this.setState({ showHiddenEventsInTimeline: value as boolean }),
|
||||
this.setState({ showHiddenEvents: value as boolean }),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
@ -389,7 +390,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!initial && this.state.roomId !== RoomViewStore.getRoomId()) {
|
||||
if (!initial && this.state.roomId !== RoomViewStore.instance.getRoomId()) {
|
||||
// RoomView explicitly does not support changing what room
|
||||
// is being viewed: instead it should just be re-mounted when
|
||||
// switching rooms. Therefore, if the room ID changes, we
|
||||
|
@ -404,28 +405,29 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const roomId = RoomViewStore.getRoomId();
|
||||
const roomId = RoomViewStore.instance.getRoomId();
|
||||
|
||||
const newState: Pick<IRoomState, any> = {
|
||||
// This convoluted type signature ensures we get IntelliSense *and* correct typing
|
||||
const newState: Partial<IRoomState> & Pick<IRoomState, any> = {
|
||||
roomId,
|
||||
roomAlias: RoomViewStore.getRoomAlias(),
|
||||
roomLoading: RoomViewStore.isRoomLoading(),
|
||||
roomLoadError: RoomViewStore.getRoomLoadError(),
|
||||
joining: RoomViewStore.isJoining(),
|
||||
replyToEvent: RoomViewStore.getQuotingEvent(),
|
||||
roomAlias: RoomViewStore.instance.getRoomAlias(),
|
||||
roomLoading: RoomViewStore.instance.isRoomLoading(),
|
||||
roomLoadError: RoomViewStore.instance.getRoomLoadError(),
|
||||
joining: RoomViewStore.instance.isJoining(),
|
||||
replyToEvent: RoomViewStore.instance.getQuotingEvent(),
|
||||
// we should only peek once we have a ready client
|
||||
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
||||
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.instance.shouldPeek(),
|
||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||
showRedactions: SettingsStore.getValue("showRedactions", roomId),
|
||||
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
|
||||
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
|
||||
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
|
||||
wasContextSwitch: RoomViewStore.getWasContextSwitch(),
|
||||
wasContextSwitch: RoomViewStore.instance.getWasContextSwitch(),
|
||||
initialEventId: null, // default to clearing this, will get set later in the method if needed
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(roomId),
|
||||
};
|
||||
|
||||
const initialEventId = RoomViewStore.getInitialEventId();
|
||||
const initialEventId = RoomViewStore.instance.getInitialEventId();
|
||||
if (initialEventId) {
|
||||
const room = this.context.getRoom(roomId);
|
||||
let initialEvent = room?.findEventById(initialEventId);
|
||||
|
@ -445,22 +447,29 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
);
|
||||
}
|
||||
|
||||
// If we have an initial event, we want to reset the event pixel offset to ensure it ends up
|
||||
// visible
|
||||
newState.initialEventPixelOffset = null;
|
||||
|
||||
const thread = initialEvent?.getThread();
|
||||
if (thread && !initialEvent?.isThreadRoot) {
|
||||
showThread({
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
|
||||
});
|
||||
} else {
|
||||
newState.initialEventId = initialEventId;
|
||||
newState.isInitialEventHighlighted = RoomViewStore.isInitialEventHighlighted();
|
||||
newState.isInitialEventHighlighted = RoomViewStore.instance.isInitialEventHighlighted();
|
||||
newState.initialEventScrollIntoView = RoomViewStore.instance.initialEventScrollIntoView();
|
||||
|
||||
if (thread && initialEvent?.isThreadRoot) {
|
||||
showThread({
|
||||
rootEvent: thread.rootEvent,
|
||||
initialEvent,
|
||||
highlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||
highlighted: RoomViewStore.instance.isInitialEventHighlighted(),
|
||||
scroll_into_view: RoomViewStore.instance.initialEventScrollIntoView(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -760,19 +769,6 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
}
|
||||
|
||||
private onUserScroll = () => {
|
||||
if (this.state.initialEventId && this.state.isInitialEventHighlighted) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.state.room.roomId,
|
||||
event_id: this.state.initialEventId,
|
||||
highlighted: false,
|
||||
replyingToEvent: this.state.replyToEvent,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private onRightPanelStoreUpdate = () => {
|
||||
this.setState({
|
||||
showRightPanel: RightPanelStore.instance.isOpenForRoom(this.state.roomId),
|
||||
|
@ -1303,6 +1299,22 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.updateTopUnreadMessagesBar();
|
||||
};
|
||||
|
||||
private resetJumpToEvent = (eventId?: string) => {
|
||||
if (this.state.initialEventId && this.state.initialEventScrollIntoView &&
|
||||
this.state.initialEventId === eventId) {
|
||||
debuglog("Removing scroll_into_view flag from initial event");
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.state.room.roomId,
|
||||
event_id: this.state.initialEventId,
|
||||
highlighted: this.state.isInitialEventHighlighted,
|
||||
scroll_into_view: false,
|
||||
replyingToEvent: this.state.replyToEvent,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private injectSticker(url: string, info: object, text: string, threadId: string | null) {
|
||||
if (this.context.isGuest()) {
|
||||
dis.dispatch({ action: 'require_registration' });
|
||||
|
@ -1347,7 +1359,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
this.handleSearchResult(searchPromise);
|
||||
};
|
||||
|
||||
private handleSearchResult(searchPromise: Promise<any>): Promise<boolean> {
|
||||
private handleSearchResult(searchPromise: Promise<ISearchResults>): Promise<boolean> {
|
||||
// keep a record of the current search id, so that if the search terms
|
||||
// change before we get a response, we can ignore the results.
|
||||
const localSearchId = this.searchId;
|
||||
|
@ -1356,7 +1368,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
searchInProgress: true,
|
||||
});
|
||||
|
||||
return searchPromise.then((results) => {
|
||||
return searchPromise.then(async (results) => {
|
||||
debuglog("search complete");
|
||||
if (this.unmounted ||
|
||||
this.state.timelineRenderingType !== TimelineRenderingType.Search ||
|
||||
|
@ -1383,6 +1395,20 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
return b.length - a.length;
|
||||
});
|
||||
|
||||
if (this.context.supportsExperimentalThreads()) {
|
||||
// Process all thread roots returned in this batch of search results
|
||||
// XXX: This won't work for results coming from Seshat which won't include the bundled relationship
|
||||
for (const result of results.results) {
|
||||
for (const event of result.context.getTimeline()) {
|
||||
const bundledRelationship = event
|
||||
.getServerAggregatedRelation<IThreadBundledRelationship>(THREAD_RELATION_TYPE.name);
|
||||
if (!bundledRelationship || event.getThread()) continue;
|
||||
const room = this.context.getRoom(event.getRoomId());
|
||||
event.setThread(room.findThreadForEvent(event) ?? room.createThread(event, [], true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
searchHighlights: highlights,
|
||||
searchResults: results,
|
||||
|
@ -1454,7 +1480,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!haveTileForEvent(mxEv, this.state.showHiddenEventsInTimeline)) {
|
||||
if (!haveRendererForEvent(mxEv, this.state.showHiddenEvents)) {
|
||||
// XXX: can this ever happen? It will make the result count
|
||||
// not match the displayed count.
|
||||
continue;
|
||||
|
@ -2053,9 +2079,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
hidden={hideMessagePanel}
|
||||
highlightedEventId={highlightedEventId}
|
||||
eventId={this.state.initialEventId}
|
||||
eventScrollIntoView={this.state.initialEventScrollIntoView}
|
||||
eventPixelOffset={this.state.initialEventPixelOffset}
|
||||
onScroll={this.onMessageListScroll}
|
||||
onUserScroll={this.onUserScroll}
|
||||
onEventScrolledIntoView={this.resetJumpToEvent}
|
||||
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
|
||||
showUrlPreview={this.state.showUrlPreview}
|
||||
className={this.messagePanelClassNames}
|
||||
|
|
|
@ -14,12 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, CSSProperties, ReactNode, SyntheticEvent, KeyboardEvent } from "react";
|
||||
import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import Timer from '../../utils/Timer';
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
@ -110,10 +109,6 @@ interface IProps {
|
|||
/* onScroll: a callback which is called whenever any scroll happens.
|
||||
*/
|
||||
onScroll?(event: Event): void;
|
||||
|
||||
/* onUserScroll: callback which is called when the user interacts with the room timeline
|
||||
*/
|
||||
onUserScroll?(event: SyntheticEvent): void;
|
||||
}
|
||||
|
||||
/* This component implements an intelligent scrolling list.
|
||||
|
@ -170,7 +165,6 @@ interface IPreventShrinkingState {
|
|||
offsetNode: HTMLElement;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ScrollPanel")
|
||||
export default class ScrollPanel extends React.Component<IProps> {
|
||||
static defaultProps = {
|
||||
stickyBottom: true,
|
||||
|
@ -595,29 +589,21 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
* @param {object} ev the keyboard event
|
||||
*/
|
||||
public handleScrollKey = (ev: KeyboardEvent) => {
|
||||
let isScrolling = false;
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
switch (roomAction) {
|
||||
case KeyBindingAction.ScrollUp:
|
||||
this.scrollRelative(-1);
|
||||
isScrolling = true;
|
||||
break;
|
||||
case KeyBindingAction.ScrollDown:
|
||||
this.scrollRelative(1);
|
||||
isScrolling = true;
|
||||
break;
|
||||
case KeyBindingAction.JumpToFirstMessage:
|
||||
this.scrollToTop();
|
||||
isScrolling = true;
|
||||
break;
|
||||
case KeyBindingAction.JumpToLatestMessage:
|
||||
this.scrollToBottom();
|
||||
isScrolling = true;
|
||||
break;
|
||||
}
|
||||
if (isScrolling && this.props.onUserScroll) {
|
||||
this.props.onUserScroll(ev);
|
||||
}
|
||||
};
|
||||
|
||||
/* Scroll the panel to bring the DOM node with the scroll token
|
||||
|
@ -967,7 +953,6 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
<AutoHideScrollbar
|
||||
wrappedRef={this.collectScroll}
|
||||
onScroll={this.onScroll}
|
||||
onWheel={this.props.onUserScroll}
|
||||
className={`mx_ScrollPanel ${this.props.className}`}
|
||||
style={this.props.style}
|
||||
>
|
||||
|
|
|
@ -20,7 +20,6 @@ import { throttle } from 'lodash';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
|
@ -43,7 +42,6 @@ interface IState {
|
|||
blurred: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.SearchBox")
|
||||
export default class SearchBox extends React.Component<IProps, IState> {
|
||||
private search = createRef<HTMLInputElement>();
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import { useTypedEventEmitterState } from "../../hooks/useEventEmitter";
|
||||
import { IOOBData } from "../../stores/ThreepidInviteStore";
|
||||
import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
|
||||
import RoomViewStore from "../../stores/RoomViewStore";
|
||||
import { RoomViewStore } from "../../stores/RoomViewStore";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
@ -371,7 +371,7 @@ export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
|
|||
metricsTrigger: "SpaceHierarchy",
|
||||
});
|
||||
}, err => {
|
||||
RoomViewStore.showJoinRoomError(err, roomId);
|
||||
RoomViewStore.instance.showJoinRoomError(err, roomId);
|
||||
});
|
||||
|
||||
return prom;
|
||||
|
|
|
@ -22,7 +22,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { _t } from '../../languageHandler';
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers";
|
||||
|
||||
|
@ -64,7 +63,6 @@ interface IState {
|
|||
activeTabIndex: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.TabbedView")
|
||||
export default class TabbedView extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -37,7 +37,7 @@ import SdkConfig from '../../SdkConfig';
|
|||
import Modal from '../../Modal';
|
||||
import BetaFeedbackDialog from '../views/dialogs/BetaFeedbackDialog';
|
||||
import { Action } from '../../dispatcher/actions';
|
||||
import { UserTab } from '../views/dialogs/UserSettingsDialog';
|
||||
import { UserTab } from '../views/dialogs/UserTab';
|
||||
import dis from '../../dispatcher/dispatcher';
|
||||
|
||||
interface IProps {
|
||||
|
@ -163,7 +163,9 @@ const EmptyThread: React.FC<EmptyThreadIProps> = ({ hasThreads, filterOption, sh
|
|||
body = <>
|
||||
<p>{ _t("Threads help keep your conversations on-topic and easy to track.") }</p>
|
||||
<p className="mx_ThreadPanel_empty_tip">
|
||||
{ _t('<b>Tip:</b> Use "Reply in thread" when hovering over a message.', {}, {
|
||||
{ _t('<b>Tip:</b> Use “%(replyInThread)s” when hovering over a message.', {
|
||||
replyInThread: _t("Reply in thread"),
|
||||
}, {
|
||||
b: sub => <b>{ sub }</b>,
|
||||
}) }
|
||||
</p>
|
||||
|
@ -250,7 +252,7 @@ const ThreadPanel: React.FC<IProps> = ({
|
|||
<RoomContext.Provider value={{
|
||||
...roomContext,
|
||||
timelineRenderingType: TimelineRenderingType.ThreadsList,
|
||||
showHiddenEventsInTimeline: true,
|
||||
showHiddenEvents: true,
|
||||
narrow,
|
||||
}}>
|
||||
<BaseCard
|
||||
|
|
|
@ -25,7 +25,6 @@ import classNames from "classnames";
|
|||
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||
import MessageComposer from '../views/rooms/MessageComposer';
|
||||
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||
|
@ -51,7 +50,7 @@ import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
|||
import Measured from '../views/elements/Measured';
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import RoomViewStore from '../../stores/RoomViewStore';
|
||||
import { RoomViewStore } from '../../stores/RoomViewStore';
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -62,6 +61,7 @@ interface IProps {
|
|||
e2eStatus?: E2EStatus;
|
||||
initialEvent?: MatrixEvent;
|
||||
isInitialEventHighlighted?: boolean;
|
||||
initialEventScrollIntoView?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -73,7 +73,6 @@ interface IState {
|
|||
narrow: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ThreadView")
|
||||
export default class ThreadView extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
@ -112,7 +111,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
room.removeListener(ThreadEvent.New, this.onNewThread);
|
||||
SettingsStore.unwatchSetting(this.layoutWatcherRef);
|
||||
|
||||
const hasRoomChanged = RoomViewStore.getRoomId() !== roomId;
|
||||
const hasRoomChanged = RoomViewStore.instance.getRoomId() !== roomId;
|
||||
if (this.props.isInitialEventHighlighted && !hasRoomChanged) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
|
@ -217,13 +216,15 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private resetHighlightedEvent = (): void => {
|
||||
if (this.props.initialEvent && this.props.isInitialEventHighlighted) {
|
||||
private resetJumpToEvent = (event?: string): void => {
|
||||
if (this.props.initialEvent && this.props.initialEventScrollIntoView &&
|
||||
event === this.props.initialEvent?.getId()) {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: this.props.room.roomId,
|
||||
event_id: this.props.initialEvent?.getId(),
|
||||
highlighted: false,
|
||||
highlighted: this.props.isInitialEventHighlighted,
|
||||
scroll_into_view: false,
|
||||
replyingToEvent: this.state.replyToEvent,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
|
@ -374,7 +375,8 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
editState={this.state.editState}
|
||||
eventId={this.props.initialEvent?.getId()}
|
||||
highlightedEventId={highlightedEventId}
|
||||
onUserScroll={this.resetHighlightedEvent}
|
||||
eventScrollIntoView={this.props.initialEventScrollIntoView}
|
||||
onEventScrolledIntoView={this.resetJumpToEvent}
|
||||
onPaginationRequest={this.onPaginationRequest}
|
||||
/>
|
||||
</div> }
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode, SyntheticEvent } from 'react';
|
||||
import React, { createRef, ReactNode } from 'react';
|
||||
import ReactDOM from "react-dom";
|
||||
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
@ -40,8 +40,6 @@ import dis from "../../dispatcher/dispatcher";
|
|||
import { Action } from '../../dispatcher/actions';
|
||||
import Timer from '../../utils/Timer';
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { arrayFastClone } from "../../utils/arrays";
|
||||
import MessagePanel from "./MessagePanel";
|
||||
import { IScrollState } from "./ScrollPanel";
|
||||
|
@ -55,6 +53,7 @@ import CallEventGrouper, { buildCallEventGroupers } from "./CallEventGrouper";
|
|||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { haveRendererForEvent } from "../../events/EventTileFactory";
|
||||
|
||||
const PAGINATE_SIZE = 20;
|
||||
const INITIAL_SIZE = 20;
|
||||
|
@ -92,6 +91,9 @@ interface IProps {
|
|||
// id of an event to jump to. If not given, will go to the end of the live timeline.
|
||||
eventId?: string;
|
||||
|
||||
// whether we should scroll the event into view
|
||||
eventScrollIntoView?: boolean;
|
||||
|
||||
// where to position the event given by eventId, in pixels from the bottom of the viewport.
|
||||
// If not given, will try to put the event half way down the viewport.
|
||||
eventPixelOffset?: number;
|
||||
|
@ -125,8 +127,7 @@ interface IProps {
|
|||
// callback which is called when the panel is scrolled.
|
||||
onScroll?(event: Event): void;
|
||||
|
||||
// callback which is called when the user interacts with the room timeline
|
||||
onUserScroll?(event: SyntheticEvent): void;
|
||||
onEventScrolledIntoView?(eventId?: string): void;
|
||||
|
||||
// callback which is called when the read-up-to mark is updated.
|
||||
onReadMarkerUpdated?(): void;
|
||||
|
@ -211,7 +212,6 @@ interface IEventIndexOpts {
|
|||
*
|
||||
* Also responsible for handling and sending read receipts.
|
||||
*/
|
||||
@replaceableComponent("structures.TimelinePanel")
|
||||
class TimelinePanel extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
|
||||
|
@ -329,9 +329,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
const differentEventId = newProps.eventId != this.props.eventId;
|
||||
const differentHighlightedEventId = newProps.highlightedEventId != this.props.highlightedEventId;
|
||||
if (differentEventId || differentHighlightedEventId) {
|
||||
logger.log("TimelinePanel switching to eventId " + newProps.eventId +
|
||||
" (was " + this.props.eventId + ")");
|
||||
const differentAvoidJump = newProps.eventScrollIntoView && !this.props.eventScrollIntoView;
|
||||
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
|
||||
logger.log("TimelinePanel switching to " +
|
||||
"eventId " + newProps.eventId + " (was " + this.props.eventId + "), " +
|
||||
"scrollIntoView: " + newProps.eventScrollIntoView + " (was " + this.props.eventScrollIntoView + ")");
|
||||
return this.initTimeline(newProps);
|
||||
}
|
||||
}
|
||||
|
@ -1125,7 +1127,41 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
offsetBase = 0.5;
|
||||
}
|
||||
|
||||
return this.loadTimeline(initialEvent, pixelOffset, offsetBase);
|
||||
return this.loadTimeline(initialEvent, pixelOffset, offsetBase, props.eventScrollIntoView);
|
||||
}
|
||||
|
||||
private scrollIntoView(eventId?: string, pixelOffset?: number, offsetBase?: number): void {
|
||||
const doScroll = () => {
|
||||
if (eventId) {
|
||||
debuglog("TimelinePanel scrolling to eventId " + eventId +
|
||||
" at position " + (offsetBase * 100) + "% + " + pixelOffset);
|
||||
this.messagePanel.current.scrollToEvent(
|
||||
eventId,
|
||||
pixelOffset,
|
||||
offsetBase,
|
||||
);
|
||||
} else {
|
||||
debuglog("TimelinePanel scrolling to bottom");
|
||||
this.messagePanel.current.scrollToBottom();
|
||||
}
|
||||
};
|
||||
|
||||
debuglog("TimelinePanel scheduling scroll to event");
|
||||
this.props.onEventScrolledIntoView?.(eventId);
|
||||
// Ensure the correct scroll position pre render, if the messages have already been loaded to DOM,
|
||||
// to avoid it jumping around
|
||||
doScroll();
|
||||
|
||||
// Ensure the correct scroll position post render for correct behaviour.
|
||||
//
|
||||
// requestAnimationFrame runs our code immediately after the DOM update but before the next repaint.
|
||||
//
|
||||
// If the messages have just been loaded for the first time, this ensures we'll repeat setting the
|
||||
// correct scroll position after React has re-rendered the TimelinePanel and MessagePanel and
|
||||
// updated the DOM.
|
||||
window.requestAnimationFrame(() => {
|
||||
doScroll();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1141,8 +1177,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* @param {number?} offsetBase the reference point for the pixelOffset. 0
|
||||
* means the top of the container, 1 means the bottom, and fractional
|
||||
* values mean somewhere in the middle. If omitted, it defaults to 0.
|
||||
*
|
||||
* @param {boolean?} scrollIntoView whether to scroll the event into view.
|
||||
*/
|
||||
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number): void {
|
||||
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number, scrollIntoView = true): void {
|
||||
this.timelineWindow = new TimelineWindow(
|
||||
MatrixClientPeg.get(), this.props.timelineSet,
|
||||
{ windowLimit: this.props.timelineCap });
|
||||
|
@ -1177,11 +1215,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
"messagePanel didn't load");
|
||||
return;
|
||||
}
|
||||
if (eventId) {
|
||||
this.messagePanel.current.scrollToEvent(eventId, pixelOffset,
|
||||
offsetBase);
|
||||
} else {
|
||||
this.messagePanel.current.scrollToBottom();
|
||||
|
||||
if (scrollIntoView) {
|
||||
this.scrollIntoView(eventId, pixelOffset, offsetBase);
|
||||
}
|
||||
|
||||
if (this.props.sendReadReceiptOnLoad) {
|
||||
|
@ -1475,7 +1511,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
const shouldIgnore = !!ev.status || // local echo
|
||||
(ignoreOwn && ev.getSender() === myUserId); // own message
|
||||
const isWithoutTile = !haveTileForEvent(ev, this.context?.showHiddenEventsInTimeline) ||
|
||||
const isWithoutTile = !haveRendererForEvent(ev, this.context?.showHiddenEvents) ||
|
||||
shouldHideEvent(ev, this.context);
|
||||
|
||||
if (isWithoutTile || !node) {
|
||||
|
@ -1632,7 +1668,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
ourUserId={MatrixClientPeg.get().credentials.userId}
|
||||
stickyBottom={stickyBottom}
|
||||
onScroll={this.onMessageListScroll}
|
||||
onUserScroll={this.props.onUserScroll}
|
||||
onFillRequest={this.onMessageListFillRequest}
|
||||
onUnfillRequest={this.onMessageListUnfillRequest}
|
||||
isTwelveHour={this.context?.showTwelveHourTimestamps ?? this.state.isTwelveHour}
|
||||
|
|
|
@ -18,14 +18,12 @@ import * as React from "react";
|
|||
import classNames from "classnames";
|
||||
|
||||
import ToastStore, { IToast } from "../../stores/ToastStore";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
||||
interface IState {
|
||||
toasts: IToast<any>[];
|
||||
countSeen: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ToastContainer")
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
@ -81,7 +79,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
|||
titleElement = (
|
||||
<div className="mx_Toast_title">
|
||||
<h2>{ title }</h2>
|
||||
<span>{ countIndicator }</span>
|
||||
<span className="mx_Toast_title_countIndicator">{ countIndicator }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import { Action } from "../../dispatcher/actions";
|
|||
import ProgressBar from "../views/elements/ProgressBar";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
||||
interface IProps {
|
||||
|
@ -40,7 +39,6 @@ interface IState {
|
|||
uploadsHere: IUpload[];
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.UploadBar")
|
||||
export default class UploadBar extends React.Component<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
|||
import { Action } from "../../dispatcher/actions";
|
||||
import { _t } from "../../languageHandler";
|
||||
import { ChevronFace, ContextMenuButton } from "./ContextMenu";
|
||||
import { UserTab } from "../views/dialogs/UserSettingsDialog";
|
||||
import { UserTab } from "../views/dialogs/UserTab";
|
||||
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
|
||||
import FeedbackDialog from "../views/dialogs/FeedbackDialog";
|
||||
import Modal from "../../Modal";
|
||||
|
@ -53,7 +53,6 @@ import { UIFeature } from "../../settings/UIFeature";
|
|||
import HostSignupAction from "./HostSignupAction";
|
||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { SettingUpdatedPayload } from "../../dispatcher/payloads/SettingUpdatedPayload";
|
||||
import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
|
||||
|
@ -143,7 +142,6 @@ const below = (rect: PartialDOMRect) => {
|
|||
};
|
||||
};
|
||||
|
||||
@replaceableComponent("structures.UserMenu")
|
||||
export default class UserMenu extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private themeWatcherRef: string;
|
||||
|
|
|
@ -23,7 +23,6 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
import HomePage from "./HomePage";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||
import MainSplit from "./MainSplit";
|
||||
import RightPanel from "./RightPanel";
|
||||
|
@ -41,7 +40,6 @@ interface IState {
|
|||
member?: RoomMember;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.UserView")
|
||||
export default class UserView extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -24,12 +24,12 @@ import { _t } from "../../languageHandler";
|
|||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import { canEditContent } from "../../utils/EventUtils";
|
||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||
import { DevtoolsContext } from "../views/dialogs/devtools/BaseTool";
|
||||
import { StateEventEditor } from "../views/dialogs/devtools/RoomState";
|
||||
import { stringify, TimelineEventEditor } from "../views/dialogs/devtools/Event";
|
||||
import CopyableText from "../views/elements/CopyableText";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
mxEvent: MatrixEvent; // the MatrixEvent associated with the context menu
|
||||
|
@ -39,7 +39,6 @@ interface IState {
|
|||
isEditing: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ViewSource")
|
||||
export default class ViewSource extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -65,29 +64,58 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
|||
// @ts-ignore
|
||||
const decryptedEventSource = mxEvent.clearEvent; // FIXME: clearEvent is private
|
||||
const originalEventSource = mxEvent.event;
|
||||
|
||||
const copyOriginalFunc = (): string => {
|
||||
return stringify(originalEventSource);
|
||||
};
|
||||
if (isEncrypted) {
|
||||
const copyDecryptedFunc = (): string => {
|
||||
return stringify(decryptedEventSource);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<details open className="mx_ViewSource_details">
|
||||
<summary>
|
||||
<span className="mx_ViewSource_heading">{ _t("Decrypted event source") }</span>
|
||||
<span className="mx_ViewSource_heading">
|
||||
{ _t("Decrypted event source") }
|
||||
</span>
|
||||
</summary>
|
||||
<SyntaxHighlight language="json">{ stringify(decryptedEventSource) }</SyntaxHighlight>
|
||||
<div className="mx_ViewSource_container">
|
||||
<CopyableText getTextToCopy={copyDecryptedFunc}>
|
||||
<SyntaxHighlight language="json">
|
||||
{ stringify(decryptedEventSource) }
|
||||
</SyntaxHighlight>
|
||||
</CopyableText>
|
||||
</div>
|
||||
</details>
|
||||
<details className="mx_ViewSource_details">
|
||||
<summary>
|
||||
<span className="mx_ViewSource_heading">{ _t("Original event source") }</span>
|
||||
<span className="mx_ViewSource_heading">
|
||||
{ _t("Original event source") }
|
||||
</span>
|
||||
</summary>
|
||||
<SyntaxHighlight language="json">{ stringify(originalEventSource) }</SyntaxHighlight>
|
||||
<div className="mx_ViewSource_container">
|
||||
<CopyableText getTextToCopy={copyOriginalFunc}>
|
||||
<SyntaxHighlight language="json">
|
||||
{ stringify(originalEventSource) }
|
||||
</SyntaxHighlight>
|
||||
</CopyableText>
|
||||
</div>
|
||||
</details>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<div className="mx_ViewSource_heading">{ _t("Original event source") }</div>
|
||||
<SyntaxHighlight language="json">{ stringify(originalEventSource) }</SyntaxHighlight>
|
||||
<div className="mx_ViewSource_heading">
|
||||
{ _t("Original event source") }
|
||||
</div>
|
||||
<div className="mx_ViewSource_container">
|
||||
<CopyableText getTextToCopy={copyOriginalFunc}>
|
||||
<SyntaxHighlight language="json">
|
||||
{ stringify(originalEventSource) }
|
||||
</SyntaxHighlight>
|
||||
</CopyableText>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -139,8 +167,16 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
|||
return (
|
||||
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t("View Source")}>
|
||||
<div>
|
||||
<div>{ _t("Room ID: %(roomId)s", { roomId }) }</div>
|
||||
<div>{ _t("Event ID: %(eventId)s", { eventId }) }</div>
|
||||
<div>
|
||||
<CopyableText getTextToCopy={() => roomId} border={false}>
|
||||
{ _t("Room ID: %(roomId)s", { roomId }) }
|
||||
</CopyableText>
|
||||
</div>
|
||||
<div>
|
||||
<CopyableText getTextToCopy={() => eventId} border={false}>
|
||||
{ _t("Event ID: %(eventId)s", { eventId }) }
|
||||
</CopyableText>
|
||||
</div>
|
||||
<div className="mx_ViewSource_separator" />
|
||||
{ isEditing ? this.editSourceContent() : this.viewSourceContent() }
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
|
||||
import AuthPage from "../../views/auth/AuthPage";
|
||||
|
@ -33,7 +32,6 @@ interface IState {
|
|||
lostKeys: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||
export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import AuthPage from '../../views/auth/AuthPage';
|
||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
onFinished: () => void;
|
||||
|
@ -27,7 +26,6 @@ interface IProps {
|
|||
tokenLogin?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.E2eSetup")
|
||||
export default class E2eSetup extends React.Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -28,7 +28,6 @@ import AuthPage from "../../views/auth/AuthPage";
|
|||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import EmailField from "../../views/auth/EmailField";
|
||||
import PassphraseField from '../../views/auth/PassphraseField';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
||||
import InlineSpinner from '../../views/elements/InlineSpinner';
|
||||
import Spinner from "../../views/elements/Spinner";
|
||||
|
@ -81,7 +80,6 @@ enum ForgotPasswordField {
|
|||
PasswordConfirm = 'field_password_confirm',
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.ForgotPassword")
|
||||
export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||
private reset: PasswordReset;
|
||||
|
||||
|
@ -224,8 +222,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onInputChanged = (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => {
|
||||
let value = ev.currentTarget.value;
|
||||
if (stateKey === "email") value = value.trim();
|
||||
this.setState({
|
||||
[stateKey]: ev.currentTarget.value,
|
||||
[stateKey]: value,
|
||||
} as any);
|
||||
};
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ import InlineSpinner from "../../views/elements/InlineSpinner";
|
|||
import Spinner from "../../views/elements/Spinner";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AuthBody from "../../views/auth/AuthBody";
|
||||
import AuthHeader from "../../views/auth/AuthHeader";
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
|
@ -103,7 +102,6 @@ interface IState {
|
|||
/*
|
||||
* A wire component which glues together login UI components and Login logic
|
||||
*/
|
||||
@replaceableComponent("structures.auth.LoginComponent")
|
||||
export default class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
private unmounted = false;
|
||||
private loginLogic: Login;
|
||||
|
|
|
@ -30,7 +30,6 @@ import Login, { ISSOFlow } from "../../../Login";
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from '../../views/elements/ServerPicker';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import RegistrationForm from '../../views/auth/RegistrationForm';
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
import AuthBody from "../../views/auth/AuthBody";
|
||||
|
@ -110,7 +109,6 @@ interface IState {
|
|||
ssoFlow?: ISSOFlow;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.Registration")
|
||||
export default class Registration extends React.Component<IProps, IState> {
|
||||
loginLogic: Login;
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import Modal from '../../../Modal';
|
||||
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
import Spinner from '../../views/elements/Spinner';
|
||||
|
@ -49,7 +48,6 @@ interface IState {
|
|||
lostKeys: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
||||
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -27,7 +27,6 @@ import { ISSOFlow, LoginFlow, sendLoginRequest } from "../../../Login";
|
|||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ConfirmWipeDeviceDialog from '../../views/dialogs/ConfirmWipeDeviceDialog';
|
||||
import Field from '../../views/elements/Field';
|
||||
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||
|
@ -70,7 +69,6 @@ interface IState {
|
|||
flows: LoginFlow[];
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.SoftLogout")
|
||||
export default class SoftLogout extends React.Component<IProps, IState> {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
26
src/components/structures/static-page-vars.ts
Normal file
26
src/components/structures/static-page-vars.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
// We're importing via require specifically so the svg becomes a URI rather than a DOM element.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const matrixSvg = require('../../../res/img/matrix.svg').default;
|
||||
|
||||
/**
|
||||
* Intended to replace $matrixLogo in the welcome page.
|
||||
*/
|
||||
export const MATRIX_LOGO_HTML = `<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
||||
<img width="79" height="34" alt="Matrix" style="padding-left: 1px;vertical-align: middle" src="${matrixSvg}"/>
|
||||
</a>`;
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ReactNode } from "react";
|
||||
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||
import DurationClock from "./DurationClock";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
@ -25,7 +24,6 @@ import SeekBar from "./SeekBar";
|
|||
import PlaybackClock from "./PlaybackClock";
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||
export default class AudioPlayer extends AudioPlayerBase {
|
||||
protected renderFileSize(): string {
|
||||
const bytes = this.props.playback.sizeBytes;
|
||||
|
|
|
@ -19,7 +19,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
@ -39,7 +38,6 @@ interface IState {
|
|||
error?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayerBase")
|
||||
export default abstract class AudioPlayerBase<T extends IProps = IProps> extends React.PureComponent<T, IState> {
|
||||
protected seekRef: RefObject<SeekBar> = createRef();
|
||||
protected playPauseRef: RefObject<PlayPauseButton> = createRef();
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { HTMLProps } from "react";
|
||||
|
||||
import { formatSeconds } from "../../../DateUtils";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
export interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live"> {
|
||||
seconds: number;
|
||||
|
@ -27,7 +26,6 @@ export interface IProps extends Pick<HTMLProps<HTMLSpanElement>, "aria-live"> {
|
|||
* Simply converts seconds into minutes and seconds. Note that hours will not be
|
||||
* displayed, making it possible to see "82:29".
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.Clock")
|
||||
export default class Clock extends React.Component<IProps> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
|
||||
|
@ -31,7 +30,6 @@ interface IState {
|
|||
/**
|
||||
* A clock which shows a clip's maximum duration.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.DurationClock")
|
||||
export default class DurationClock extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
||||
|
@ -32,7 +31,6 @@ interface IState {
|
|||
/**
|
||||
* A clock for a live recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.LiveRecordingClock")
|
||||
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
|
||||
private seconds = 0;
|
||||
private scheduledUpdate = new MarkedExecution(
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from "react";
|
||||
|
||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arrayFastResample, arraySeed } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
@ -33,7 +32,6 @@ interface IState {
|
|||
/**
|
||||
* A waveform which shows the waveform of a live recording
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.LiveRecordingWaveform")
|
||||
export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
progress: 1,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
|
@ -35,7 +34,6 @@ interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButto
|
|||
* Displays a play/pause button (activating the play/pause function of the recorder)
|
||||
* to be displayed in reference to a recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlayPauseButton")
|
||||
export default class PlayPauseButton extends React.PureComponent<IProps> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
@ -39,7 +38,6 @@ interface IState {
|
|||
/**
|
||||
* A clock for a playback of a recording.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlaybackClock")
|
||||
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
|
||||
|
@ -34,7 +33,6 @@ interface IState {
|
|||
/**
|
||||
* A waveform which shows the waveform of a previously recorded recording
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.PlaybackWaveform")
|
||||
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -18,7 +18,6 @@ import React, { ReactNode } from "react";
|
|||
|
||||
import PlayPauseButton from "./PlayPauseButton";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AudioPlayerBase, { IProps as IAudioPlayerBaseProps } from "./AudioPlayerBase";
|
||||
import SeekBar from "./SeekBar";
|
||||
import PlaybackWaveform from "./PlaybackWaveform";
|
||||
|
@ -30,7 +29,6 @@ interface IProps extends IAudioPlayerBaseProps {
|
|||
withWaveform?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||
export default class RecordingPlayback extends AudioPlayerBase<IProps> {
|
||||
// This component is rendered in two ways: the composer and timeline. They have different
|
||||
// rendering properties (specifically the difference of a waveform or not).
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
||||
|
@ -43,7 +42,6 @@ interface ISeekCSS extends CSSProperties {
|
|||
|
||||
const ARROW_SKIP_SECONDS = 5; // arbitrary
|
||||
|
||||
@replaceableComponent("views.audio_messages.SeekBar")
|
||||
export default class SeekBar extends React.PureComponent<IProps, IState> {
|
||||
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
|
||||
// really using anything demanding on the CSS front.
|
||||
|
|
|
@ -17,8 +17,6 @@ limitations under the License.
|
|||
import React, { CSSProperties } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface WaveformCSSProperties extends CSSProperties {
|
||||
'--barHeight': number;
|
||||
}
|
||||
|
@ -39,7 +37,6 @@ interface IState {
|
|||
* For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
|
||||
* "filled", as a demonstration of the progress property.
|
||||
*/
|
||||
@replaceableComponent("views.audio_messages.Waveform")
|
||||
export default class Waveform extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
progress: 1,
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthBody")
|
||||
export default class AuthBody extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_AuthBody">
|
||||
|
|
|
@ -19,9 +19,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthFooter")
|
||||
export default class AuthFooter extends React.Component {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AuthHeaderLogo from "./AuthHeaderLogo";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
|
||||
|
@ -25,7 +24,6 @@ interface IProps {
|
|||
disableLanguageSelector?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeader")
|
||||
export default class AuthHeader extends React.Component<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeaderLogo")
|
||||
export default class AuthHeaderLogo extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_AuthHeaderLogo">
|
||||
|
|
|
@ -18,10 +18,8 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AuthFooter from "./AuthFooter";
|
||||
|
||||
@replaceableComponent("views.auth.AuthPage")
|
||||
export default class AuthPage extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,6 @@ import React, { createRef } from 'react';
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
const DIV_ID = 'mx_recaptcha';
|
||||
|
||||
|
@ -35,7 +34,6 @@ interface ICaptchaFormState {
|
|||
/**
|
||||
* A pure UI component which displays a captcha form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.CaptchaForm")
|
||||
export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICaptchaFormState> {
|
||||
static defaultProps = {
|
||||
onCaptchaResponse: () => {},
|
||||
|
|
|
@ -16,9 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.CompleteSecurityBody")
|
||||
export default class CompleteSecurityBody extends React.PureComponent {
|
||||
public render(): React.ReactNode {
|
||||
return <div className="mx_CompleteSecurityBody">
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from '../../../phonenumber';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
|
||||
const COUNTRIES_BY_ISO2 = {};
|
||||
|
@ -53,7 +52,6 @@ interface IState {
|
|||
defaultCountry: PhoneNumberCountryDefinition;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.CountryDropdown")
|
||||
export default class CountryDropdown extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React, { PureComponent, RefCallback, RefObject } from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
|
@ -39,7 +38,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult): void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailField")
|
||||
class EmailField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Email"),
|
||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { LocalisedPolicy, Policies } from '../../../Terms';
|
||||
import Field from '../elements/Field';
|
||||
import CaptchaForm from "./CaptchaForm";
|
||||
|
@ -93,7 +92,6 @@ interface IPasswordAuthEntryState {
|
|||
password: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.PasswordAuthEntry")
|
||||
export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswordAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Password;
|
||||
|
||||
|
@ -191,7 +189,6 @@ interface IRecaptchaAuthEntryProps extends IAuthEntryProps {
|
|||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
@replaceableComponent("views.auth.RecaptchaAuthEntry")
|
||||
export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps> {
|
||||
static LOGIN_TYPE = AuthType.Recaptcha;
|
||||
|
||||
|
@ -262,7 +259,6 @@ interface ITermsAuthEntryState {
|
|||
errorText?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.TermsAuthEntry")
|
||||
export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITermsAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Terms;
|
||||
|
||||
|
@ -409,7 +405,6 @@ interface IEmailIdentityAuthEntryProps extends IAuthEntryProps {
|
|||
};
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailIdentityAuthEntry")
|
||||
export class EmailIdentityAuthEntry extends React.Component<IEmailIdentityAuthEntryProps> {
|
||||
static LOGIN_TYPE = AuthType.Email;
|
||||
|
||||
|
@ -472,7 +467,6 @@ interface IMsisdnAuthEntryState {
|
|||
errorText: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.MsisdnAuthEntry")
|
||||
export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsisdnAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Msisdn;
|
||||
|
||||
|
@ -623,7 +617,6 @@ interface ISSOAuthEntryState {
|
|||
attemptFailed: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.SSOAuthEntry")
|
||||
export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEntryState> {
|
||||
static LOGIN_TYPE = AuthType.Sso;
|
||||
static UNSTABLE_LOGIN_TYPE = AuthType.SsoUnstable;
|
||||
|
@ -743,7 +736,6 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.FallbackAuthEntry")
|
||||
export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
|
||||
private popupWindow: Window;
|
||||
private fallbackButton = createRef<HTMLButtonElement>();
|
||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
|||
|
||||
import React, { PureComponent, RefCallback, RefObject } from "react";
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
|
@ -35,7 +34,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailField")
|
||||
class PassphraseConfirmField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Confirm password"),
|
||||
|
|
|
@ -22,7 +22,6 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
|
||||
import { _t, _td } from "../../../languageHandler";
|
||||
import Field, { IInputProps } from "../elements/Field";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends Omit<IInputProps, "onValidate"> {
|
||||
autoFocus?: boolean;
|
||||
|
@ -41,7 +40,6 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate?(result: IValidationResult);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.PassphraseField")
|
||||
class PassphraseField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Password"),
|
||||
|
|
|
@ -24,7 +24,6 @@ import AccessibleButton from "../elements/AccessibleButton";
|
|||
import withValidation, { IValidationResult } from "../elements/Validation";
|
||||
import Field from "../elements/Field";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import EmailField from "./EmailField";
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
|
@ -66,7 +65,6 @@ enum LoginField {
|
|||
* A pure UI component which displays a username/password form.
|
||||
* The email/username/phone fields are fully-controlled, the password field is not.
|
||||
*/
|
||||
@replaceableComponent("views.auth.PasswordLogin")
|
||||
export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onUsernameChanged: function() {},
|
||||
|
|
|
@ -31,7 +31,6 @@ import EmailField from "./EmailField";
|
|||
import PassphraseField from "./PassphraseField";
|
||||
import Field from '../elements/Field';
|
||||
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
import PassphraseConfirmField from "./PassphraseConfirmField";
|
||||
|
||||
|
@ -92,7 +91,6 @@ interface IState {
|
|||
/*
|
||||
* A pure UI component which displays a registration form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.RegistrationForm")
|
||||
export default class RegistrationForm extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onValidationChange: logger.error,
|
||||
|
|
|
@ -17,14 +17,14 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import classNames from "classnames";
|
||||
|
||||
import * as sdk from "../../../index";
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import AuthPage from "./AuthPage";
|
||||
import { _td } from "../../../languageHandler";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
import EmbeddedPage from "../../structures/EmbeddedPage";
|
||||
import { MATRIX_LOGO_HTML } from "../../structures/static-page-vars";
|
||||
|
||||
// translatable strings for Welcome pages
|
||||
_td("Sign in with SSO");
|
||||
|
@ -33,12 +33,8 @@ interface IProps {
|
|||
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.Welcome")
|
||||
export default class Welcome extends React.PureComponent<IProps> {
|
||||
public render(): React.ReactNode {
|
||||
// FIXME: Using an import will result in wrench-element-tests failures
|
||||
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
|
||||
|
||||
const pagesConfig = SdkConfig.getObject("embedded_pages");
|
||||
let pageUrl = null;
|
||||
if (pagesConfig) {
|
||||
|
@ -59,6 +55,8 @@ export default class Welcome extends React.PureComponent<IProps> {
|
|||
replaceMap={{
|
||||
"$riot:ssoUrl": "#/start_sso",
|
||||
"$riot:casUrl": "#/start_cas",
|
||||
"$matrixLogo": MATRIX_LOGO_HTML,
|
||||
"[matrix]": MATRIX_LOGO_HTML,
|
||||
}}
|
||||
/>
|
||||
<LanguageSelector />
|
||||
|
|
|
@ -32,7 +32,6 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { _t } from "../../../languageHandler";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||
import TooltipTarget from "../elements/TooltipTarget";
|
||||
|
||||
|
@ -78,7 +77,6 @@ function tooltipText(variant: Icon) {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.DecoratedRoomAvatar")
|
||||
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
||||
private _dmUser: User;
|
||||
private isUnmounted = false;
|
||||
|
|
|
@ -23,9 +23,8 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import BaseAvatar from "./BaseAvatar";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { CardContext } from '../right_panel/BaseCard';
|
||||
import { CardContext } from '../right_panel/context';
|
||||
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -52,7 +51,6 @@ interface IState {
|
|||
imageUrl?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.MemberAvatar")
|
||||
export default class MemberAvatar extends React.PureComponent<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
width: 40,
|
||||
|
|
|
@ -28,7 +28,6 @@ import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
|||
import Modal from '../../../Modal';
|
||||
import * as Avatar from '../../../Avatar';
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
||||
|
||||
|
@ -52,7 +51,6 @@ interface IState {
|
|||
urls: string[];
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.RoomAvatar")
|
||||
export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
width: 36,
|
||||
|
|
65
src/components/views/beacon/BeaconMarker.tsx
Normal file
65
src/components/views/beacon/BeaconMarker.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2022 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, { useContext } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import {
|
||||
Beacon,
|
||||
BeaconEvent,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
|
||||
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import SmartMarker from '../location/SmartMarker';
|
||||
|
||||
interface Props {
|
||||
map: maplibregl.Map;
|
||||
beacon: Beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a map SmartMarker with latest location from given beacon
|
||||
*/
|
||||
const BeaconMarker: React.FC<Props> = ({ map, beacon }) => {
|
||||
const latestLocationState = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.LocationUpdate,
|
||||
() => beacon.latestLocationState,
|
||||
);
|
||||
const matrixClient = useContext(MatrixClientContext);
|
||||
const room = matrixClient.getRoom(beacon.roomId);
|
||||
|
||||
if (!latestLocationState || !beacon.isLive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const geoUri = latestLocationState?.uri;
|
||||
|
||||
const markerRoomMember = beacon.beaconInfo.assetType === LocationAssetType.Self ?
|
||||
room.getMember(beacon.beaconInfoOwner) :
|
||||
undefined;
|
||||
|
||||
return <SmartMarker
|
||||
map={map}
|
||||
id={beacon.identifier}
|
||||
geoUri={geoUri}
|
||||
roomMember={markerRoomMember}
|
||||
useMemberColor
|
||||
/>;
|
||||
};
|
||||
|
||||
export default BeaconMarker;
|
84
src/components/views/beacon/BeaconStatus.tsx
Normal file
84
src/components/views/beacon/BeaconStatus.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2022 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, { HTMLProps } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import LiveTimeRemaining from './LiveTimeRemaining';
|
||||
import { BeaconDisplayStatus } from './displayStatus';
|
||||
import { getBeaconExpiryTimestamp } from '../../../utils/beacon';
|
||||
import { formatTime } from '../../../DateUtils';
|
||||
|
||||
interface Props {
|
||||
displayStatus: BeaconDisplayStatus;
|
||||
displayLiveTimeRemaining?: boolean;
|
||||
beacon?: Beacon;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const BeaconExpiryTime: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const expiryTime = formatTime(new Date(getBeaconExpiryTimestamp(beacon)));
|
||||
return <span className='mx_BeaconStatus_expiryTime'>{ _t('Live until %(expiryTime)s', { expiryTime }) }</span>;
|
||||
};
|
||||
|
||||
const BeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> =
|
||||
({
|
||||
beacon,
|
||||
displayStatus,
|
||||
displayLiveTimeRemaining,
|
||||
label,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const isIdle = displayStatus === BeaconDisplayStatus.Loading ||
|
||||
displayStatus === BeaconDisplayStatus.Stopped;
|
||||
|
||||
return <div
|
||||
{...rest}
|
||||
className={classNames('mx_BeaconStatus', `mx_BeaconStatus_${displayStatus}`, className)}
|
||||
>
|
||||
<StyledLiveBeaconIcon
|
||||
className='mx_BeaconStatus_icon'
|
||||
withError={displayStatus === BeaconDisplayStatus.Error}
|
||||
isIdle={isIdle}
|
||||
/>
|
||||
<div className='mx_BeaconStatus_description'>
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Loading && <span>{ _t('Loading live location...') }</span> }
|
||||
{ displayStatus === BeaconDisplayStatus.Stopped && <span>{ _t('Live location ended') }</span> }
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Error && <span>{ _t('Live location error') }</span> }
|
||||
|
||||
{ displayStatus === BeaconDisplayStatus.Active && beacon && <>
|
||||
<>
|
||||
{ label }
|
||||
{ displayLiveTimeRemaining ?
|
||||
<LiveTimeRemaining beacon={beacon} /> :
|
||||
<BeaconExpiryTime beacon={beacon} />
|
||||
}
|
||||
</>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
{ children }
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default BeaconStatus;
|
116
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
116
src/components/views/beacon/BeaconViewDialog.tsx
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright 2022 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 { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import {
|
||||
Beacon,
|
||||
Room,
|
||||
} from 'matrix-js-sdk/src/matrix';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
|
||||
import { useLiveBeacons } from '../../../utils/beacon/useLiveBeacons';
|
||||
import MatrixClientContext from '../../../contexts/MatrixClientContext';
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
||||
import Map from '../location/Map';
|
||||
import ZoomButtons from '../location/ZoomButtons';
|
||||
import BeaconMarker from './BeaconMarker';
|
||||
import { Bounds, getBeaconBounds } from '../../../utils/beacon/bounds';
|
||||
import { getGeoUri } from '../../../utils/beacon';
|
||||
import { Icon as LocationIcon } from '../../../../res/img/element-icons/location.svg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: Room['roomId'];
|
||||
matrixClient: MatrixClient;
|
||||
// open the map centered on this beacon's location
|
||||
focusBeacon?: Beacon;
|
||||
}
|
||||
|
||||
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
||||
if (!bounds) {
|
||||
return;
|
||||
}
|
||||
return getGeoUri({
|
||||
latitude: (bounds.north + bounds.south) / 2,
|
||||
longitude: (bounds.east + bounds.west) / 2,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Dialog to view live beacons maximised
|
||||
*/
|
||||
const BeaconViewDialog: React.FC<IProps> = ({
|
||||
focusBeacon,
|
||||
roomId,
|
||||
matrixClient,
|
||||
onFinished,
|
||||
}) => {
|
||||
const liveBeacons = useLiveBeacons(roomId, matrixClient);
|
||||
|
||||
const bounds = getBeaconBounds(liveBeacons);
|
||||
const centerGeoUri = focusBeacon?.latestLocationState?.uri || getBoundsCenter(bounds);
|
||||
|
||||
return (
|
||||
<BaseDialog
|
||||
className='mx_BeaconViewDialog'
|
||||
onFinished={onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
{ !!bounds ? <Map
|
||||
id='mx_BeaconViewDialog'
|
||||
bounds={bounds}
|
||||
centerGeoUri={centerGeoUri}
|
||||
interactive
|
||||
className="mx_BeaconViewDialog_map"
|
||||
>
|
||||
{
|
||||
({ map }: { map: maplibregl.Map}) =>
|
||||
<>
|
||||
{ liveBeacons.map(beacon => <BeaconMarker
|
||||
key={beacon.identifier}
|
||||
map={map}
|
||||
beacon={beacon}
|
||||
/>) }
|
||||
<ZoomButtons map={map} />
|
||||
</>
|
||||
}
|
||||
</Map> :
|
||||
<div
|
||||
data-test-id='beacon-view-dialog-map-fallback'
|
||||
className='mx_BeaconViewDialog_map mx_BeaconViewDialog_mapFallback'
|
||||
>
|
||||
<LocationIcon className='mx_BeaconViewDialog_mapFallbackIcon' />
|
||||
<span className='mx_BeaconViewDialog_mapFallbackMessage'>{ _t('No live locations') }</span>
|
||||
<AccessibleButton
|
||||
kind='primary'
|
||||
onClick={onFinished}
|
||||
data-test-id='beacon-view-dialog-fallback-close'
|
||||
>
|
||||
{ _t('Close') }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
}
|
||||
</MatrixClientContext.Provider>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BeaconViewDialog;
|
75
src/components/views/beacon/LiveTimeRemaining.tsx
Normal file
75
src/components/views/beacon/LiveTimeRemaining.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2022 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, { useCallback, useEffect, useState } from 'react';
|
||||
import { BeaconEvent, Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import { formatDuration } from '../../../DateUtils';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { useInterval } from '../../../hooks/useTimeout';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { getBeaconMsUntilExpiry } from '../../../utils/beacon';
|
||||
|
||||
const MINUTE_MS = 60000;
|
||||
const HOUR_MS = MINUTE_MS * 60;
|
||||
const getUpdateInterval = (ms: number) => {
|
||||
// every 10 mins when more than an hour
|
||||
if (ms > HOUR_MS) {
|
||||
return MINUTE_MS * 10;
|
||||
}
|
||||
// every minute when more than a minute
|
||||
if (ms > MINUTE_MS) {
|
||||
return MINUTE_MS;
|
||||
}
|
||||
// otherwise every second
|
||||
return 1000;
|
||||
};
|
||||
const useMsRemaining = (beacon: Beacon): number => {
|
||||
const beaconInfo = useEventEmitterState(
|
||||
beacon,
|
||||
BeaconEvent.Update,
|
||||
() => beacon.beaconInfo,
|
||||
);
|
||||
|
||||
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beaconInfo));
|
||||
|
||||
useEffect(() => {
|
||||
setMsRemaining(getBeaconMsUntilExpiry(beaconInfo));
|
||||
}, [beaconInfo]);
|
||||
|
||||
const updateMsRemaining = useCallback(() => {
|
||||
const ms = getBeaconMsUntilExpiry(beaconInfo);
|
||||
setMsRemaining(ms);
|
||||
}, [beaconInfo]);
|
||||
|
||||
useInterval(updateMsRemaining, getUpdateInterval(msRemaining));
|
||||
|
||||
return msRemaining;
|
||||
};
|
||||
|
||||
const LiveTimeRemaining: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const msRemaining = useMsRemaining(beacon);
|
||||
|
||||
const timeRemaining = formatDuration(msRemaining);
|
||||
const liveTimeRemaining = _t(`%(timeRemaining)s left`, { timeRemaining });
|
||||
|
||||
return <span
|
||||
data-test-id='room-live-share-expiry'
|
||||
className="mx_LiveTimeRemaining"
|
||||
>{ liveTimeRemaining }</span>;
|
||||
};
|
||||
|
||||
export default LiveTimeRemaining;
|
89
src/components/views/beacon/OwnBeaconStatus.tsx
Normal file
89
src/components/views/beacon/OwnBeaconStatus.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
Copyright 2022 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 { Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
import React, { HTMLProps } from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { useOwnLiveBeacons } from '../../../utils/beacon';
|
||||
import BeaconStatus from './BeaconStatus';
|
||||
import { BeaconDisplayStatus } from './displayStatus';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
|
||||
interface Props {
|
||||
displayStatus: BeaconDisplayStatus;
|
||||
beacon?: Beacon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps BeaconStatus with more capabilities
|
||||
* for errors and actions available for users own live beacons
|
||||
*/
|
||||
const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({
|
||||
beacon, displayStatus, className, ...rest
|
||||
}) => {
|
||||
const {
|
||||
hasWireError,
|
||||
hasStopSharingError,
|
||||
stoppingInProgress,
|
||||
onStopSharing,
|
||||
onResetWireError,
|
||||
} = useOwnLiveBeacons([beacon?.identifier]);
|
||||
|
||||
// combine display status with errors that only occur for user's own beacons
|
||||
const ownDisplayStatus = hasWireError || hasStopSharingError ?
|
||||
BeaconDisplayStatus.Error :
|
||||
displayStatus;
|
||||
|
||||
return <BeaconStatus
|
||||
className='mx_MBeaconBody_chin'
|
||||
beacon={beacon}
|
||||
displayStatus={ownDisplayStatus}
|
||||
label={_t('Live location enabled')}
|
||||
displayLiveTimeRemaining
|
||||
{...rest}
|
||||
>
|
||||
{ ownDisplayStatus === BeaconDisplayStatus.Active && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon'
|
||||
kind='link'
|
||||
onClick={onStopSharing}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
disabled={stoppingInProgress}
|
||||
>
|
||||
{ _t('Stop') }
|
||||
</AccessibleButton>
|
||||
}
|
||||
{ hasWireError && <AccessibleButton
|
||||
data-test-id='beacon-status-reset-wire-error'
|
||||
kind='link'
|
||||
onClick={onResetWireError}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
{ _t('Retry') }
|
||||
</AccessibleButton>
|
||||
}
|
||||
{ hasStopSharingError && <AccessibleButton
|
||||
data-test-id='beacon-status-stop-beacon-retry'
|
||||
kind='link'
|
||||
onClick={onStopSharing}
|
||||
className='mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton'
|
||||
>
|
||||
{ _t('Retry') }
|
||||
</AccessibleButton> }
|
||||
</BeaconStatus>;
|
||||
};
|
||||
|
||||
export default OwnBeaconStatus;
|
|
@ -14,126 +14,19 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Room, Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
import { Room } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import { formatDuration } from '../../../DateUtils';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { useEventEmitterState } from '../../../hooks/useEventEmitter';
|
||||
import { useInterval } from '../../../hooks/useTimeout';
|
||||
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../stores/OwnBeaconStore';
|
||||
import { getBeaconMsUntilExpiry, sortBeaconsByLatestExpiry } from '../../../utils/beacon';
|
||||
import { useOwnLiveBeacons } from '../../../utils/beacon';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import StyledLiveBeaconIcon from './StyledLiveBeaconIcon';
|
||||
import { Icon as CloseIcon } from '../../../../res/img/image-view/close.svg';
|
||||
|
||||
const MINUTE_MS = 60000;
|
||||
const HOUR_MS = MINUTE_MS * 60;
|
||||
|
||||
const getUpdateInterval = (ms: number) => {
|
||||
// every 10 mins when more than an hour
|
||||
if (ms > HOUR_MS) {
|
||||
return MINUTE_MS * 10;
|
||||
}
|
||||
// every minute when more than a minute
|
||||
if (ms > MINUTE_MS) {
|
||||
return MINUTE_MS;
|
||||
}
|
||||
// otherwise every second
|
||||
return 1000;
|
||||
};
|
||||
const useMsRemaining = (beacon: Beacon): number => {
|
||||
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beacon));
|
||||
|
||||
useEffect(() => {
|
||||
setMsRemaining(getBeaconMsUntilExpiry(beacon));
|
||||
}, [beacon]);
|
||||
|
||||
const updateMsRemaining = useCallback(() => {
|
||||
const ms = getBeaconMsUntilExpiry(beacon);
|
||||
setMsRemaining(ms);
|
||||
}, [beacon]);
|
||||
|
||||
useInterval(updateMsRemaining, getUpdateInterval(msRemaining));
|
||||
|
||||
return msRemaining;
|
||||
};
|
||||
|
||||
/**
|
||||
* It's technically possible to have multiple live beacons in one room
|
||||
* Select the latest expiry to display,
|
||||
* and kill all beacons on stop sharing
|
||||
*/
|
||||
type LiveBeaconsState = {
|
||||
beacon?: Beacon;
|
||||
onStopSharing?: () => void;
|
||||
onResetWireError?: () => void;
|
||||
stoppingInProgress?: boolean;
|
||||
hasStopSharingError?: boolean;
|
||||
hasWireError?: boolean;
|
||||
};
|
||||
const useLiveBeacons = (liveBeaconIds: string[], roomId: string): LiveBeaconsState => {
|
||||
const [stoppingInProgress, setStoppingInProgress] = useState(false);
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
const hasWireError = useEventEmitterState(
|
||||
OwnBeaconStore.instance,
|
||||
OwnBeaconStoreEvent.WireError,
|
||||
() =>
|
||||
OwnBeaconStore.instance.hasWireErrors(roomId),
|
||||
);
|
||||
|
||||
// reset stopping in progress on change in live ids
|
||||
useEffect(() => {
|
||||
setStoppingInProgress(false);
|
||||
setError(undefined);
|
||||
}, [liveBeaconIds]);
|
||||
|
||||
// select the beacon with latest expiry to display expiry time
|
||||
const beacon = liveBeaconIds.map(beaconId => OwnBeaconStore.instance.getBeaconById(beaconId))
|
||||
.sort(sortBeaconsByLatestExpiry)
|
||||
.shift();
|
||||
|
||||
const onStopSharing = async () => {
|
||||
setStoppingInProgress(true);
|
||||
try {
|
||||
await Promise.all(liveBeaconIds.map(beaconId => OwnBeaconStore.instance.stopBeacon(beaconId)));
|
||||
} catch (error) {
|
||||
// only clear loading in case of error
|
||||
// to avoid flash of not-loading state
|
||||
// after beacons have been stopped but we wait for sync
|
||||
setError(error);
|
||||
setStoppingInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onResetWireError = () => {
|
||||
liveBeaconIds.map(beaconId => OwnBeaconStore.instance.resetWireError(beaconId));
|
||||
};
|
||||
|
||||
return {
|
||||
onStopSharing,
|
||||
onResetWireError,
|
||||
beacon,
|
||||
stoppingInProgress,
|
||||
hasWireError,
|
||||
hasStopSharingError: !!error,
|
||||
};
|
||||
};
|
||||
|
||||
const LiveTimeRemaining: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
|
||||
const msRemaining = useMsRemaining(beacon);
|
||||
|
||||
const timeRemaining = formatDuration(msRemaining);
|
||||
const liveTimeRemaining = _t(`%(timeRemaining)s left`, { timeRemaining });
|
||||
|
||||
return <span
|
||||
data-test-id='room-live-share-expiry'
|
||||
className="mx_RoomLiveShareWarning_expiry"
|
||||
>{ liveTimeRemaining }</span>;
|
||||
};
|
||||
import LiveTimeRemaining from './LiveTimeRemaining';
|
||||
|
||||
const getLabel = (hasWireError: boolean, hasStopSharingError: boolean): string => {
|
||||
if (hasWireError) {
|
||||
|
@ -157,7 +50,7 @@ const RoomLiveShareWarningInner: React.FC<RoomLiveShareWarningInnerProps> = ({ l
|
|||
stoppingInProgress,
|
||||
hasStopSharingError,
|
||||
hasWireError,
|
||||
} = useLiveBeacons(liveBeaconIds, roomId);
|
||||
} = useOwnLiveBeacons(liveBeaconIds);
|
||||
|
||||
if (!beacon) {
|
||||
return null;
|
||||
|
@ -188,6 +81,7 @@ const RoomLiveShareWarningInner: React.FC<RoomLiveShareWarningInnerProps> = ({ l
|
|||
{ !stoppingInProgress && !hasError && <LiveTimeRemaining beacon={beacon} /> }
|
||||
|
||||
<AccessibleButton
|
||||
className='mx_RoomLiveShareWarning_stopButton'
|
||||
data-test-id='room-live-share-primary-button'
|
||||
onClick={onButtonClick}
|
||||
kind='danger'
|
||||
|
|
|
@ -22,11 +22,19 @@ import { Icon as LiveLocationIcon } from '../../../../res/img/location/live-loca
|
|||
interface Props extends React.SVGProps<SVGSVGElement> {
|
||||
// use error styling when true
|
||||
withError?: boolean;
|
||||
isIdle?: boolean;
|
||||
}
|
||||
const StyledLiveBeaconIcon: React.FC<Props> = ({ className, withError, ...props }) =>
|
||||
const StyledLiveBeaconIcon: React.FC<Props> = ({ className, withError, isIdle, ...props }) =>
|
||||
<LiveLocationIcon
|
||||
{...props}
|
||||
className={classNames('mx_StyledLiveBeaconIcon', className, { 'mx_StyledLiveBeaconIcon_error': withError })}
|
||||
className={classNames(
|
||||
'mx_StyledLiveBeaconIcon',
|
||||
className,
|
||||
{
|
||||
'mx_StyledLiveBeaconIcon_error': withError,
|
||||
'mx_StyledLiveBeaconIcon_idle': isIdle,
|
||||
|
||||
})}
|
||||
/>;
|
||||
|
||||
export default StyledLiveBeaconIcon;
|
||||
|
|
42
src/components/views/beacon/displayStatus.ts
Normal file
42
src/components/views/beacon/displayStatus.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2022 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 { BeaconLocationState } from "matrix-js-sdk/src/content-helpers";
|
||||
|
||||
export enum BeaconDisplayStatus {
|
||||
Loading = 'Loading',
|
||||
Error = 'Error',
|
||||
Stopped = 'Stopped',
|
||||
Active = 'Active',
|
||||
}
|
||||
export const getBeaconDisplayStatus = (
|
||||
isLive: boolean,
|
||||
latestLocationState?: BeaconLocationState,
|
||||
error?: Error): BeaconDisplayStatus => {
|
||||
if (error) {
|
||||
return BeaconDisplayStatus.Error;
|
||||
}
|
||||
if (!isLive) {
|
||||
return BeaconDisplayStatus.Stopped;
|
||||
}
|
||||
|
||||
if (!latestLocationState) {
|
||||
return BeaconDisplayStatus.Loading;
|
||||
}
|
||||
if (latestLocationState) {
|
||||
return BeaconDisplayStatus.Active;
|
||||
}
|
||||
};
|
|
@ -21,13 +21,11 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import ContextMenu, { IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu';
|
||||
import CallHandler from '../../../CallHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.CallContextMenu")
|
||||
export default class CallContextMenu extends React.Component<IProps> {
|
||||
static propTypes = {
|
||||
// js-sdk User object. Not required because it might not exist.
|
||||
|
|
|
@ -22,7 +22,6 @@ import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
|||
import ContextMenu, { IProps as IContextMenuProps } from '../../structures/ContextMenu';
|
||||
import Field from "../elements/Field";
|
||||
import DialPad from '../voip/DialPad';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
call: MatrixCall;
|
||||
|
@ -32,7 +31,6 @@ interface IState {
|
|||
value: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.DialpadContextMenu")
|
||||
export default class DialpadContextMenu extends React.Component<IProps, IState> {
|
||||
private numberEntryFieldRef: React.RefObject<Field> = createRef();
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
element: React.ReactNode;
|
||||
// Function to be called when the parent window is resized
|
||||
|
@ -30,7 +28,6 @@ interface IProps {
|
|||
* This component can be used to display generic HTML content in a contextual
|
||||
* menu.
|
||||
*/
|
||||
@replaceableComponent("views.context_menus.GenericElementContextMenu")
|
||||
export default class GenericElementContextMenu extends React.Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
message: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.GenericTextContextMenu")
|
||||
export default class GenericTextContextMenu extends React.Component<IProps> {
|
||||
public render(): JSX.Element {
|
||||
return <div className="mx_Tooltip mx_Tooltip_visible" style={{ display: "block" }}>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 - 2022 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,12 +16,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||
import { RoomMemberEvent } from "matrix-js-sdk/src/models/room-member";
|
||||
import { M_LOCATION } from 'matrix-js-sdk/src/@types/location';
|
||||
import { M_POLL_START } from "matrix-events-sdk";
|
||||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
|
@ -30,79 +30,81 @@ import Modal from '../../../Modal';
|
|||
import Resend from '../../../Resend';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||
import { isContentActionable } from '../../../utils/EventUtils';
|
||||
import { canEditContent, canForward, editEvent, isContentActionable, isLocationEvent } from '../../../utils/EventUtils';
|
||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
||||
import ForwardDialog from "../dialogs/ForwardDialog";
|
||||
import { ReadPinsEventId } from "../right_panel/types";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import ReportEventDialog from '../dialogs/ReportEventDialog';
|
||||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||
import { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { copyPlaintext, getSelectedText } from '../../../utils/strings';
|
||||
import ContextMenu, { toRightOf } from '../../structures/ContextMenu';
|
||||
import ReactionPicker from '../emojipicker/ReactionPicker';
|
||||
import ViewSource from '../../structures/ViewSource';
|
||||
import { createRedactEventDialog } from '../dialogs/ConfirmRedactDialog';
|
||||
import ShareDialog from '../dialogs/ShareDialog';
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { ChevronFace, IPosition } from '../../structures/ContextMenu';
|
||||
import { IPosition, ChevronFace } from '../../structures/ContextMenu';
|
||||
import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext';
|
||||
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
||||
import EndPollDialog from '../dialogs/EndPollDialog';
|
||||
import { isPollEnded } from '../messages/MPollBody';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
|
||||
import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwardDialogPayload";
|
||||
import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
|
||||
import { createMapSiteLink } from '../../../utils/location';
|
||||
|
||||
export function canCancel(status: EventStatus): boolean {
|
||||
return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING;
|
||||
}
|
||||
|
||||
export interface IEventTileOps {
|
||||
isWidgetHidden(): boolean;
|
||||
unhideWidget(): void;
|
||||
}
|
||||
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
}
|
||||
|
||||
interface IProps extends IPosition {
|
||||
chevronFace: ChevronFace;
|
||||
/* the MatrixEvent associated with the context menu */
|
||||
mxEvent: MatrixEvent;
|
||||
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
|
||||
// An optional EventTileOps implementation that can be used to unhide preview widgets
|
||||
eventTileOps?: IEventTileOps;
|
||||
// Callback called when the menu is dismissed
|
||||
permalinkCreator?: RoomPermalinkCreator;
|
||||
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
|
||||
collapseReplyChain?(): void;
|
||||
/* callback called when the menu is dismissed */
|
||||
onFinished(): void;
|
||||
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
|
||||
// If the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding)
|
||||
onCloseDialog?(): void;
|
||||
getRelationsForEvent?: (
|
||||
eventId: string,
|
||||
relationType: string,
|
||||
eventType: string
|
||||
) => Relations;
|
||||
// True if the menu is being used as a right click menu
|
||||
rightClick?: boolean;
|
||||
// The Relations model from the JS SDK for reactions to `mxEvent`
|
||||
reactions?: Relations;
|
||||
// A permalink to the event
|
||||
showPermalink?: boolean;
|
||||
|
||||
getRelationsForEvent?: GetRelationsForEvent;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
canRedact: boolean;
|
||||
canPin: boolean;
|
||||
reactionPickerDisplayed: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.context_menus.MessageContextMenu")
|
||||
export default class MessageContextMenu extends React.Component<IProps, IState> {
|
||||
static contextType = RoomContext;
|
||||
public context!: React.ContextType<typeof RoomContext>;
|
||||
|
||||
state = {
|
||||
canRedact: false,
|
||||
canPin: false,
|
||||
};
|
||||
private reactButtonRef = createRef<any>(); // XXX Ref to a functional component
|
||||
|
||||
componentDidMount() {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
canRedact: false,
|
||||
canPin: false,
|
||||
reactionPickerDisplayed: false,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
MatrixClientPeg.get().on(RoomMemberEvent.PowerLevel, this.checkPermissions);
|
||||
this.checkPermissions();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
public componentWillUnmount(): void {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (cli) {
|
||||
cli.removeListener(RoomMemberEvent.PowerLevel, this.checkPermissions);
|
||||
|
@ -155,9 +157,10 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
};
|
||||
|
||||
private onReportEventClick = (): void => {
|
||||
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
||||
mxEvent: this.props.mxEvent,
|
||||
}, 'mx_Dialog_reportEvent');
|
||||
dis.dispatch<OpenReportEventDialogPayload>({
|
||||
action: Action.OpenReportEventDialog,
|
||||
event: this.props.mxEvent,
|
||||
});
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
|
@ -178,8 +181,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
};
|
||||
|
||||
private onForwardClick = (): void => {
|
||||
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
dis.dispatch<OpenForwardDialogPayload>({
|
||||
action: Action.OpenForwardDialog,
|
||||
event: this.props.mxEvent,
|
||||
permalinkCreator: this.props.permalinkCreator,
|
||||
});
|
||||
|
@ -234,11 +237,45 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCopyPermalinkClick = (e: ButtonEvent): void => {
|
||||
e.preventDefault(); // So that we don't open the permalink
|
||||
copyPlaintext(this.getPermalink());
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCollapseReplyChainClick = (): void => {
|
||||
this.props.collapseReplyChain();
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onCopyClick = (): void => {
|
||||
copyPlaintext(getSelectedText());
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onEditClick = (): void => {
|
||||
editEvent(this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent);
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onReplyClick = (): void => {
|
||||
dis.dispatch({
|
||||
action: 'reply_to_event',
|
||||
event: this.props.mxEvent,
|
||||
context: this.context.timelineRenderingType,
|
||||
});
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onReactClick = (): void => {
|
||||
this.setState({ reactionPickerDisplayed: true });
|
||||
};
|
||||
|
||||
private onCloseReactionPicker = (): void => {
|
||||
this.setState({ reactionPickerDisplayed: false });
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onEndPollClick = (): void => {
|
||||
const matrixClient = MatrixClientPeg.get();
|
||||
Modal.createTrackedDialog('End Poll', '', EndPollDialog, {
|
||||
|
@ -259,11 +296,16 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
});
|
||||
}
|
||||
|
||||
private getPermalink(): string {
|
||||
if (!this.props.permalinkCreator) return;
|
||||
return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
}
|
||||
|
||||
private getUnsentReactions(): MatrixEvent[] {
|
||||
return this.getReactions(e => e.status === EventStatus.NOT_SENT);
|
||||
}
|
||||
|
||||
private viewInRoom = () => {
|
||||
private viewInRoom = (): void => {
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
event_id: this.props.mxEvent.getId(),
|
||||
|
@ -274,39 +316,35 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
this.closeMenu();
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): JSX.Element {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const me = cli.getUserId();
|
||||
const mxEvent = this.props.mxEvent;
|
||||
const { mxEvent, rightClick, showPermalink, eventTileOps, reactions, collapseReplyChain } = this.props;
|
||||
const eventStatus = mxEvent.status;
|
||||
const unsentReactionsCount = this.getUnsentReactions().length;
|
||||
|
||||
let openInMapSiteButton: JSX.Element;
|
||||
let endPollButton: JSX.Element;
|
||||
let resendReactionsButton: JSX.Element;
|
||||
let redactButton: JSX.Element;
|
||||
let forwardButton: JSX.Element;
|
||||
let pinButton: JSX.Element;
|
||||
let unhidePreviewButton: JSX.Element;
|
||||
let externalURLButton: JSX.Element;
|
||||
let quoteButton: JSX.Element;
|
||||
let collapseReplyChain: JSX.Element;
|
||||
let redactItemList: JSX.Element;
|
||||
|
||||
const contentActionable = isContentActionable(mxEvent);
|
||||
const permalink = this.getPermalink();
|
||||
// status is SENT before remote-echo, null after
|
||||
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
|
||||
if (!mxEvent.isRedacted()) {
|
||||
if (unsentReactionsCount !== 0) {
|
||||
resendReactionsButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconResend"
|
||||
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
|
||||
onClick={this.onResendReactionsClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const { timelineRenderingType, canReact, canSendMessages } = this.context;
|
||||
const isThread = (
|
||||
timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
timelineRenderingType === TimelineRenderingType.ThreadsList
|
||||
);
|
||||
const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent;
|
||||
|
||||
let resendReactionsButton: JSX.Element;
|
||||
if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) {
|
||||
resendReactionsButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconResend"
|
||||
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
|
||||
onClick={this.onResendReactionsClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let redactButton: JSX.Element;
|
||||
if (isSent && this.state.canRedact) {
|
||||
redactButton = (
|
||||
<IconizedContextMenuOption
|
||||
|
@ -317,6 +355,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
let openInMapSiteButton: JSX.Element;
|
||||
if (this.canOpenInMapSite(mxEvent)) {
|
||||
const mapSiteLink = createMapSiteLink(mxEvent);
|
||||
openInMapSiteButton = (
|
||||
|
@ -336,26 +375,26 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (isContentActionable(mxEvent)) {
|
||||
if (canForward(mxEvent)) {
|
||||
forwardButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconForward"
|
||||
label={_t("Forward")}
|
||||
onClick={this.onForwardClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let forwardButton: JSX.Element;
|
||||
if (contentActionable && canForward(mxEvent)) {
|
||||
forwardButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconForward"
|
||||
label={_t("Forward")}
|
||||
onClick={this.onForwardClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.canPin) {
|
||||
pinButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPin"
|
||||
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
|
||||
onClick={this.onPinClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let pinButton: JSX.Element;
|
||||
if (contentActionable && this.state.canPin) {
|
||||
pinButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPin"
|
||||
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
|
||||
onClick={this.onPinClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let viewSourceButton: JSX.Element;
|
||||
|
@ -369,40 +408,42 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) {
|
||||
if (this.props.eventTileOps.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconUnhidePreview"
|
||||
label={_t("Show preview")}
|
||||
onClick={this.onUnhidePreviewClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let unhidePreviewButton: JSX.Element;
|
||||
if (eventTileOps?.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconUnhidePreview"
|
||||
label={_t("Show preview")}
|
||||
onClick={this.onUnhidePreviewClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let permalink: string | null = null;
|
||||
let permalinkButton: ReactElement | null = null;
|
||||
if (this.props.permalinkCreator) {
|
||||
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||
}
|
||||
permalinkButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPermalink"
|
||||
onClick={this.onPermalinkClick}
|
||||
label={_t('Share')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
...{
|
||||
href: permalink,
|
||||
target: "_blank",
|
||||
rel: "noreferrer noopener",
|
||||
let permalinkButton: JSX.Element;
|
||||
if (permalink) {
|
||||
permalinkButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName={showPermalink
|
||||
? "mx_MessageContextMenu_iconCopy"
|
||||
: "mx_MessageContextMenu_iconPermalink"
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
onClick={showPermalink ? this.onCopyPermalinkClick : this.onPermalinkClick}
|
||||
label={showPermalink ? _t('Copy link') : _t('Share')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
...{
|
||||
|
||||
href: permalink,
|
||||
target: "_blank",
|
||||
rel: "noreferrer noopener",
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let endPollButton: JSX.Element;
|
||||
if (this.canEndPoll(mxEvent)) {
|
||||
endPollButton = (
|
||||
<IconizedContextMenuOption
|
||||
|
@ -413,7 +454,8 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) { // this event is rendered using TextualBody
|
||||
let quoteButton: JSX.Element;
|
||||
if (eventTileOps) { // this event is rendered using TextualBody
|
||||
quoteButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconQuote"
|
||||
|
@ -424,7 +466,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
// Bridges can provide a 'external_url' to link back to the source.
|
||||
if (typeof (mxEvent.getContent().external_url) === "string" &&
|
||||
let externalURLButton: JSX.Element;
|
||||
if (
|
||||
typeof (mxEvent.getContent().external_url) === "string" &&
|
||||
isUrlPermitted(mxEvent.getContent().external_url)
|
||||
) {
|
||||
externalURLButton = (
|
||||
|
@ -445,8 +489,9 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
if (this.props.collapseReplyChain) {
|
||||
collapseReplyChain = (
|
||||
let collapseReplyChainButton: JSX.Element;
|
||||
if (collapseReplyChain) {
|
||||
collapseReplyChainButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconCollapse"
|
||||
label={_t("Collapse reply thread")}
|
||||
|
@ -466,20 +511,86 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
const { timelineRenderingType } = this.context;
|
||||
const isThread = (
|
||||
timelineRenderingType === TimelineRenderingType.Thread ||
|
||||
timelineRenderingType === TimelineRenderingType.ThreadsList
|
||||
);
|
||||
const isThreadRootEvent = isThread && this.props.mxEvent.isThreadRoot;
|
||||
let copyButton: JSX.Element;
|
||||
if (rightClick && getSelectedText()) {
|
||||
copyButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconCopy"
|
||||
label={_t("Copy")}
|
||||
triggerOnMouseDown={true} // We use onMouseDown so that the selection isn't cleared when we click
|
||||
onClick={this.onCopyClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const commonItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ isThreadRootEvent && <IconizedContextMenuOption
|
||||
let editButton: JSX.Element;
|
||||
if (rightClick && canEditContent(mxEvent)) {
|
||||
editButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconEdit"
|
||||
label={_t("Edit")}
|
||||
onClick={this.onEditClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let replyButton: JSX.Element;
|
||||
if (rightClick && contentActionable && canSendMessages) {
|
||||
replyButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconReply"
|
||||
label={_t("Reply")}
|
||||
onClick={this.onReplyClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let reactButton;
|
||||
if (rightClick && contentActionable && canReact) {
|
||||
reactButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconReact"
|
||||
label={_t("React")}
|
||||
onClick={this.onReactClick}
|
||||
inputRef={this.reactButtonRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let viewInRoomButton: JSX.Element;
|
||||
if (isThreadRootEvent) {
|
||||
viewInRoomButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconViewInRoom"
|
||||
label={_t("View in room")}
|
||||
onClick={this.viewInRoom}
|
||||
/> }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let nativeItemsList: JSX.Element;
|
||||
if (copyButton) {
|
||||
nativeItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ copyButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
let quickItemsList: JSX.Element;
|
||||
if (editButton || replyButton || reactButton) {
|
||||
quickItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ reactButton }
|
||||
{ replyButton }
|
||||
{ editButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
const commonItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{ viewInRoomButton }
|
||||
{ openInMapSiteButton }
|
||||
{ endPollButton }
|
||||
{ quoteButton }
|
||||
|
@ -491,10 +602,11 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
{ unhidePreviewButton }
|
||||
{ viewSourceButton }
|
||||
{ resendReactionsButton }
|
||||
{ collapseReplyChain }
|
||||
{ collapseReplyChainButton }
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
|
||||
let redactItemList: JSX.Element;
|
||||
if (redactButton) {
|
||||
redactItemList = (
|
||||
<IconizedContextMenuOptionList red>
|
||||
|
@ -502,33 +614,40 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
}
|
||||
|
||||
let reactionPicker: JSX.Element;
|
||||
if (this.state.reactionPickerDisplayed) {
|
||||
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
|
||||
reactionPicker = (
|
||||
<ContextMenu
|
||||
{...toRightOf(buttonRect)}
|
||||
onFinished={this.closeMenu}
|
||||
managed={false}
|
||||
>
|
||||
<ReactionPicker
|
||||
mxEvent={mxEvent}
|
||||
onFinished={this.onCloseReactionPicker}
|
||||
reactions={reactions}
|
||||
/>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IconizedContextMenu
|
||||
{...this.props}
|
||||
className="mx_MessageContextMenu"
|
||||
compact={true}
|
||||
>
|
||||
{ commonItemsList }
|
||||
{ redactItemList }
|
||||
</IconizedContextMenu>
|
||||
<React.Fragment>
|
||||
<IconizedContextMenu
|
||||
{...this.props}
|
||||
className="mx_MessageContextMenu"
|
||||
compact={true}
|
||||
>
|
||||
{ nativeItemsList }
|
||||
{ quickItemsList }
|
||||
{ commonItemsList }
|
||||
{ redactItemList }
|
||||
</IconizedContextMenu>
|
||||
{ reactionPicker }
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function canForward(event: MatrixEvent): boolean {
|
||||
return !(
|
||||
isLocationEvent(event) ||
|
||||
M_POLL_START.matches(event.getType())
|
||||
);
|
||||
}
|
||||
|
||||
function isLocationEvent(event: MatrixEvent): boolean {
|
||||
const eventType = event.getType();
|
||||
return (
|
||||
M_LOCATION.matches(eventType) ||
|
||||
(
|
||||
eventType === EventType.RoomMessage &&
|
||||
M_LOCATION.matches(event.getContent().msgtype)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ import Modal from "../../../Modal";
|
|||
import ExportDialog from "../dialogs/ExportDialog";
|
||||
import { useFeatureEnabled } from "../../../hooks/useSettings";
|
||||
import { usePinnedEvents } from "../right_panel/PinnedMessagesCard";
|
||||
import RoomViewStore from "../../../stores/RoomViewStore";
|
||||
import { RoomViewStore } from "../../../stores/RoomViewStore";
|
||||
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
|
||||
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
|
||||
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||
|
@ -328,7 +328,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
};
|
||||
|
||||
const ensureViewingRoom = (ev: ButtonEvent) => {
|
||||
if (RoomViewStore.getRoomId() === room.roomId) return;
|
||||
if (RoomViewStore.instance.getRoomId() === room.roomId) return;
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
|
@ -373,7 +373,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
ev.stopPropagation();
|
||||
|
||||
Modal.createDialog(DevtoolsDialog, {
|
||||
roomId: RoomViewStore.getRoomId(),
|
||||
roomId: RoomViewStore.instance.getRoomId(),
|
||||
}, "mx_DevtoolsDialog_wrapper");
|
||||
onFinished();
|
||||
}}
|
||||
|
|
|
@ -22,7 +22,6 @@ import { IProps as IContextMenuProps } from "../../structures/ContextMenu";
|
|||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import {
|
||||
leaveSpace,
|
||||
shouldShowSpaceSettings,
|
||||
showCreateNewRoom,
|
||||
showCreateNewSubspace,
|
||||
|
@ -30,6 +29,7 @@ import {
|
|||
showSpacePreferences,
|
||||
showSpaceSettings,
|
||||
} from "../../../utils/space";
|
||||
import { leaveSpace } from "../../../utils/leave-behaviour";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface IProps {
|
||||
|
@ -32,7 +31,6 @@ interface IProps {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.AskInviteAnywayDialog")
|
||||
export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
||||
private onInviteClicked = (): void => {
|
||||
this.props.onInviteAnyways();
|
||||
|
|
|
@ -25,7 +25,6 @@ import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
|||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Heading from '../typography/Heading';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
|
||||
|
@ -81,7 +80,6 @@ interface IProps extends IDialogProps {
|
|||
* Includes a div for the title, and a keypress handler which cancels the
|
||||
* dialog on escape.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.BaseDialog")
|
||||
export default class BaseDialog extends React.Component<IProps> {
|
||||
private matrixClient: MatrixClient;
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { UserTab } from "./UserSettingsDialog";
|
||||
import { UserTab } from "./UserTab";
|
||||
import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
|
||||
|
||||
// XXX: Keep this around for re-use in future Betas
|
||||
|
|
|
@ -24,7 +24,6 @@ import Modal from '../../../Modal';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import sendBugReport, { downloadBugReport } from '../../../rageshake/submit-rageshake';
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import QuestionDialog from "./QuestionDialog";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import Field from '../elements/Field';
|
||||
|
@ -50,7 +49,6 @@ interface IState {
|
|||
downloadProgress: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.BugReportDialog")
|
||||
export default class BugReportDialog extends React.Component<IProps, IState> {
|
||||
private unmounted: boolean;
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ConfirmRedactDialog from './ConfirmRedactDialog';
|
||||
import ErrorDialog from './ErrorDialog';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
@ -45,7 +44,6 @@ interface IState {
|
|||
*
|
||||
* To avoid this, we keep the dialog open as long as /redact is in progress.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmAndWaitRedactDialog")
|
||||
export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
|||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ErrorDialog from './ErrorDialog';
|
||||
import TextInputDialog from "./TextInputDialog";
|
||||
|
||||
|
@ -31,7 +30,6 @@ interface IProps {
|
|||
/*
|
||||
* A dialog for confirming a redaction.
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
|
||||
export default class ConfirmRedactDialog extends React.Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -19,7 +19,6 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|||
import classNames from "classnames";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
@ -55,7 +54,6 @@ interface IState {
|
|||
* to make it obvious what is going to happen.
|
||||
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
||||
*/
|
||||
@replaceableComponent("views.dialogs.ConfirmUserActionDialog")
|
||||
export default class ConfirmUserActionDialog extends React.Component<IProps, IState> {
|
||||
static defaultProps = {
|
||||
danger: false,
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
|
@ -25,7 +24,6 @@ interface IProps {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ConfirmWipeDeviceDialog")
|
||||
export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
||||
private onConfirm = (): void => {
|
||||
this.props.onFinished(true);
|
||||
|
|
|
@ -24,8 +24,7 @@ import SdkConfig from '../../../SdkConfig';
|
|||
import withValidation, { IFieldState } from '../elements/Validation';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { IOpts, privateShouldBeEncrypted } from "../../../createRoom";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IOpts } from "../../../createRoom";
|
||||
import Field from "../elements/Field";
|
||||
import RoomAliasField from "../elements/RoomAliasField";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
|
@ -35,6 +34,7 @@ import SpaceStore from "../../../stores/spaces/SpaceStore";
|
|||
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||
|
||||
interface IProps {
|
||||
type?: RoomType;
|
||||
|
@ -58,7 +58,6 @@ interface IState {
|
|||
canChangeEncryption: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.CreateRoomDialog")
|
||||
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
private readonly supportsRestricted: boolean;
|
||||
private nameField = createRef<Field>();
|
||||
|
@ -104,7 +103,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
createOpts.preset = Preset.PublicChat;
|
||||
opts.guestAccess = false;
|
||||
const { alias } = this.state;
|
||||
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
||||
createOpts.room_alias_name = alias.substring(1, alias.indexOf(":"));
|
||||
} else {
|
||||
// If we cannot change encryption we pass `true` for safety, the server should automatically do this for us.
|
||||
opts.encryption = this.state.canChangeEncryption ? this.state.isEncrypted : true;
|
||||
|
|
|
@ -21,13 +21,13 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import Analytics from '../../../Analytics';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/InteractiveAuth";
|
||||
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
|
||||
interface IProps {
|
||||
onFinished: (success: boolean) => void;
|
||||
|
@ -46,7 +46,6 @@ interface IState {
|
|||
continueKind: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.DeactivateAccountDialog")
|
||||
export default class DeactivateAccountDialog extends React.Component<IProps, IState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -124,7 +123,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
|||
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
|
||||
// Deactivation worked - logout & close this dialog
|
||||
Analytics.trackEvent('Account', 'Deactivate Account');
|
||||
Lifecycle.onLoggedOut();
|
||||
defaultDispatcher.fire(Action.TriggerLogout);
|
||||
this.props.onFinished(true);
|
||||
}).catch(e => {
|
||||
logger.error(e);
|
||||
|
|
|
@ -28,7 +28,6 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
|
||||
interface IProps {
|
||||
|
@ -44,7 +43,6 @@ interface IState {
|
|||
onFinished: (success: boolean) => void;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.ErrorDialog")
|
||||
export default class ErrorDialog extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
focus: true,
|
||||
|
|
|
@ -43,10 +43,10 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
|||
import TruncatedList from "../elements/TruncatedList";
|
||||
import EntityTile from "../rooms/EntityTile";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import { roomContextDetailsText } from "../../../Rooms";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { roomContextDetailsText } from "../../../utils/i18n-helpers";
|
||||
|
||||
const AVATAR_SIZE = 30;
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import {
|
|||
IPostmessageResponseData,
|
||||
PostmessageAction,
|
||||
} from "./HostSignupDialogTypes";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IConfigOptions } from "../../../IConfigOptions";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
|
||||
|
@ -46,7 +45,6 @@ interface IState {
|
|||
minimized: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.HostSignupDialog")
|
||||
export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
|
||||
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||
private readonly config: SnakedObject<IConfigOptions["host_signup"]>;
|
||||
|
|
|
@ -21,7 +21,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import VerificationComplete from "../verification/VerificationComplete";
|
||||
import VerificationCancelled from "../verification/VerificationCancelled";
|
||||
|
@ -54,7 +53,6 @@ interface IState {
|
|||
sas: IGeneratedSas;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.IncomingSasDialog")
|
||||
export default class IncomingSasDialog extends React.Component<IProps, IState> {
|
||||
private showSasEvent: ISasEvent;
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ import React, { ReactNode, KeyboardEvent } from 'react';
|
|||
import classNames from "classnames";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
title?: string;
|
||||
|
@ -44,9 +45,6 @@ export default class InfoDialog extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
render() {
|
||||
// FIXME: Using a regular import will break the app
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
return (
|
||||
<BaseDialog
|
||||
className="mx_InfoDialog"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue