Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Make room ID copyable #7600

Merged
merged 3 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
47 changes: 47 additions & 0 deletions res/css/views/elements/_CopyableText.scss
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
32 changes: 1 addition & 31 deletions res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;
}
}
}
63 changes: 63 additions & 0 deletions src/components/views/elements/CopyableText.tsx
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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>
Expand Down
59 changes: 10 additions & 49 deletions src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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';
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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>
Expand All @@ -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'>
Expand Down
4 changes: 2 additions & 2 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -1547,7 +1546,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",
Expand Down Expand Up @@ -2220,6 +2219,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",
Expand Down