Make room ID copyable (#7600)

pull/21833/head
Šimon Brandner 2022-01-24 12:47:59 +01:00 committed by GitHub
parent 5f18e4888c
commit a2f1e856be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 85 deletions

View File

@ -133,6 +133,7 @@
@import "./views/elements/_AccessibleButton.scss";
@import "./views/elements/_AddressSelector.scss";
@import "./views/elements/_AddressTile.scss";
@import "./views/elements/_CopyableText.scss";
@import "./views/elements/_DesktopBuildsNotice.scss";
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
@import "./views/elements/_DialPadBackspaceButton.scss";

View File

@ -0,0 +1,47 @@
/*
Copyright 2019 New Vector Ltd
Copyright 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.
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.
*/
.mx_CopyableText {
display: flex;
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 10px;
padding: 10px;
width: max-content;
max-width: 100%;
.mx_CopyableText_copyButton {
flex-shrink: 0;
width: 20px;
height: 20px;
cursor: pointer;
margin-left: 20px;
display: block;
&::before {
content: "";
mask-image: url($copy-button-url);
background-color: $message-action-bar-fg-color;
width: 20px;
height: 20px;
display: block;
background-repeat: no-repeat;
}
}
}

View File

@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 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.
@ -27,34 +28,3 @@ limitations under the License.
word-break: break-all;
user-select: all;
}
.mx_HelpUserSettingsTab_copy {
display: flex;
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 10px;
padding: 10px;
width: max-content;
max-width: 100%;
.mx_HelpUserSettingsTab_copyButton {
flex-shrink: 0;
width: 20px;
height: 20px;
cursor: pointer;
margin-left: 20px;
display: block;
&::before {
content: "";
mask-image: url($copy-button-url);
background-color: $message-action-bar-fg-color;
width: 20px;
height: 20px;
display: block;
background-repeat: no-repeat;
}
}
}

View File

@ -0,0 +1,63 @@
/*
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
Copyright 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.
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, { useEffect, useRef } from "react";
import { _t } from "../../../languageHandler";
import { copyPlaintext } from "../../../utils/strings";
import { toRightOf, createMenu } from "../../structures/ContextMenu";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
interface IProps {
children: React.ReactNode;
getTextToCopy: () => string;
}
const CopyableText: React.FC<IProps> = ({ children, getTextToCopy }) => {
const closeCopiedTooltip = useRef<() => void>();
const divRef = useRef<HTMLDivElement>();
useEffect(() => () => {
if (closeCopiedTooltip.current) closeCopiedTooltip.current();
}, [closeCopiedTooltip]);
const onCopyClickInternal = async (e: ButtonEvent) => {
e.preventDefault();
const target = e.target as HTMLDivElement; // copy target before we go async and React throws it away
const successful = await copyPlaintext(getTextToCopy());
const buttonRect = target.getBoundingClientRect();
const { close } = createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
closeCopiedTooltip.current = target.onmouseleave = close;
};
return <div className="mx_CopyableText" ref={divRef}>
{ children }
<AccessibleTooltipButton
title={_t("Copy")}
onClick={onCopyClickInternal}
className="mx_CopyableText_copyButton"
/>
</div>;
};
export default CopyableText;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2019 - 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.
@ -26,6 +26,7 @@ import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher";
import { Action } from '../../../../../dispatcher/actions';
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import CopyableText from "../../../elements/CopyableText";
interface IProps {
roomId: string;
@ -149,8 +150,10 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
{ room?.isSpaceRoom() ? _t("Space information") : _t("Room information") }
</span>
<div>
<span>{ _t("Internal room ID:") }</span>&nbsp;
{ this.props.roomId }
<span>{ _t("Internal room ID") }</span>
<CopyableText getTextToCopy={() => this.props.roomId}>
{ this.props.roomId }
</CopyableText>
</div>
{ unfederatableSection }
</div>

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2019-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.
@ -17,25 +17,21 @@ limitations under the License.
import React from 'react';
import { logger } from "matrix-js-sdk/src/logger";
import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
import AccessibleButton from "../../../elements/AccessibleButton";
import { _t, getCurrentLanguage } from "../../../../../languageHandler";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton';
import SdkConfig from "../../../../../SdkConfig";
import createRoom from "../../../../../createRoom";
import Modal from "../../../../../Modal";
import PlatformPeg from "../../../../../PlatformPeg";
import UpdateCheckButton from "../../UpdateCheckButton";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { copyPlaintext } from "../../../../../utils/strings";
import * as ContextMenu from "../../../../structures/ContextMenu";
import { toRightOf } from "../../../../structures/ContextMenu";
import BugReportDialog from '../../../dialogs/BugReportDialog';
import GenericTextContextMenu from "../../../context_menus/GenericTextContextMenu";
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
import { Action } from "../../../../../dispatcher/actions";
import { UserTab } from "../../../dialogs/UserSettingsDialog";
import dis from "../../../../../dispatcher/dispatcher";
import CopyableText from "../../../elements/CopyableText";
interface IProps {
closeSettingsFn: () => void;
@ -48,8 +44,6 @@ interface IState {
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
protected closeCopiedTooltip: () => void;
constructor(props) {
super(props);
@ -68,12 +62,6 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
});
}
componentWillUnmount() {
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
// the tooltip otherwise, such as pressing Escape
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
}
private getVersionInfo(): { appVersion: string, olmVersion: string } {
const brand = SdkConfig.get().brand;
const appVersion = this.state.appVersion || 'unknown';
@ -192,26 +180,9 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
);
}
private async copy(text: string, e: ButtonEvent) {
e.preventDefault();
const target = e.target as HTMLDivElement; // copy target before we go async and React throws it away
const successful = await copyPlaintext(text);
const buttonRect = target.getBoundingClientRect();
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
this.closeCopiedTooltip = target.onmouseleave = close;
}
private onAccessTokenCopyClick = (e: ButtonEvent) => {
this.copy(MatrixClientPeg.get().getAccessToken(), e);
};
private onCopyVersionClicked = (e: ButtonEvent) => {
private getVersionTextToCopy = (): string => {
const { appVersion, olmVersion } = this.getVersionInfo();
this.copy(`${appVersion}\n${olmVersion}`, e);
return `${appVersion}\n${olmVersion}`;
};
private onKeyboardShortcutsClicked = (): void => {
@ -324,15 +295,10 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
<span className='mx_SettingsTab_subheading'>{ _t("Versions") }</span>
<div className='mx_SettingsTab_subsectionText'>
<div className="mx_HelpUserSettingsTab_copy">
<CopyableText getTextToCopy={this.getVersionTextToCopy}>
{ appVersion }<br />
{ olmVersion }<br />
<AccessibleTooltipButton
title={_t("Copy")}
onClick={this.onCopyVersionClicked}
className="mx_HelpUserSettingsTab_copyButton"
/>
</div>
</CopyableText>
{ updateButton }
</div>
</div>
@ -348,14 +314,9 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
<summary>{ _t("Access Token") }</summary><br />
<b>{ _t("Your access token gives full access to your account."
+ " Do not share it with anyone.") }</b>
<div className="mx_HelpUserSettingsTab_copy">
<code>{ MatrixClientPeg.get().getAccessToken() }</code>
<AccessibleTooltipButton
title={_t("Copy")}
onClick={this.onAccessTokenCopyClick}
className="mx_HelpUserSettingsTab_copyButton"
/>
</div>
<CopyableText getTextToCopy={() => MatrixClientPeg.get().getAccessToken()}>
{ MatrixClientPeg.get().getAccessToken() }
</CopyableText>
</details><br />
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>

View File

@ -1425,7 +1425,6 @@
"FAQ": "FAQ",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Versions": "Versions",
"Copy": "Copy",
"Homeserver is": "Homeserver is",
"Identity server is": "Identity server is",
"Access Token": "Access Token",
@ -1531,7 +1530,7 @@
"this room": "this room",
"View older messages in %(roomName)s.": "View older messages in %(roomName)s.",
"Space information": "Space information",
"Internal room ID:": "Internal room ID:",
"Internal room ID": "Internal room ID",
"Room version": "Room version",
"Room version:": "Room version:",
"Developer options": "Developer options",
@ -2204,6 +2203,7 @@
"Error loading Widget": "Error loading Widget",
"Error - Mixed content": "Error - Mixed content",
"Popout widget": "Popout widget",
"Copy": "Copy",
"Message search initialisation failed, check <a>your settings</a> for more information": "Message search initialisation failed, check <a>your settings</a> for more information",
"Use the <a>Desktop app</a> to see all encrypted files": "Use the <a>Desktop app</a> to see all encrypted files",
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages",