Reuse CopyableText component in all places it can be (#7701)

pull/21833/head
Michael Telatynski 2022-02-02 12:16:00 +00:00 committed by GitHub
parent 292971dd0e
commit 75b03ca101
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 26 additions and 135 deletions

View File

@ -130,12 +130,8 @@ limitations under the License.
text-transform: uppercase; text-transform: uppercase;
} }
.mx_InviteDialog_footer_link { .mx_CopyableText {
display: flex; width: unset; // full width
justify-content: space-between;
border-radius: 4px;
border: solid 1px $light-fg-color;
padding: 8px;
> a { > a {
text-decoration: none; text-decoration: none;
@ -144,22 +140,6 @@ limitations under the License.
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
.mx_InviteDialog_footer_link_copy {
flex-shrink: 0;
cursor: pointer;
margin-left: 20px;
display: inherit;
> div {
mask-image: url($copy-button-url);
background-color: $message-action-bar-fg-color;
margin-left: 5px;
width: 20px;
height: 20px;
background-repeat: no-repeat;
}
}
} }
.mx_InviteDialog_roomTile { .mx_InviteDialog_roomTile {

View File

@ -20,44 +20,20 @@ limitations under the License.
border-color: $light-fg-color; border-color: $light-fg-color;
} }
.mx_ShareDialog_content { .mx_ShareDialog .mx_ShareDialog_content {
margin: 10px 0; margin: 10px 0;
}
.mx_ShareDialog_matrixto { .mx_CopyableText {
display: flex; width: unset; // full width
justify-content: space-between;
border-radius: 5px;
border: solid 1px $light-fg-color;
margin-bottom: 10px;
margin-top: 30px;
padding: 10px;
}
.mx_ShareDialog_matrixto a { > a {
text-decoration: none; text-decoration: none;
} flex-shrink: 1;
overflow: hidden;
.mx_ShareDialog_matrixto_link { text-overflow: ellipsis;
flex-shrink: 1; white-space: nowrap;
overflow: hidden; }
text-overflow: ellipsis; }
}
.mx_ShareDialog_matrixto_copy {
flex-shrink: 0;
cursor: pointer;
margin-left: 20px;
display: inherit;
}
.mx_ShareDialog_matrixto_copy::after {
content: "";
mask-image: url($copy-button-url);
background-color: $message-action-bar-fg-color;
margin-left: 5px;
width: 20px;
height: 20px;
background-repeat: no-repeat;
} }
.mx_ShareDialog_split { .mx_ShareDialog_split {

View File

@ -17,6 +17,7 @@ limitations under the License.
.mx_CopyableText { .mx_CopyableText {
display: flex; display: flex;
justify-content: space-between;
border-radius: 5px; border-radius: 5px;
border: solid 1px $light-fg-color; border: solid 1px $light-fg-color;
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -58,11 +58,7 @@ import { mediaFromMxc } from "../../../customisations/Media";
import { getAddressType } from "../../../UserAddress"; import { getAddressType } from "../../../UserAddress";
import BaseAvatar from '../avatars/BaseAvatar'; import BaseAvatar from '../avatars/BaseAvatar';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton'; import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import { compare, copyPlaintext, selectText } from '../../../utils/strings'; import { compare, selectText } from '../../../utils/strings';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import * as ContextMenu from "../../structures/ContextMenu";
import { toRightOf } from "../../structures/ContextMenu";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
import Field from '../elements/Field'; import Field from '../elements/Field';
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView'; import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
import Dialpad from '../voip/DialPad'; import Dialpad from '../voip/DialPad';
@ -73,6 +69,7 @@ import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceStore from "../../../stores/spaces/SpaceStore";
import CallHandler from "../../../CallHandler"; import CallHandler from "../../../CallHandler";
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier'; import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import CopyableText from "../elements/CopyableText";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here. // we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */ /* eslint-disable camelcase */
@ -1310,20 +1307,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
selectText(e.target); selectText(e.target);
} }
private onCopyClick = async e => {
e.preventDefault();
const target = e.target; // copy target before we go async and React throws it away
const successful = await copyPlaintext(makeUserPermalink(MatrixClientPeg.get().getUserId()));
const buttonRect = target.getBoundingClientRect();
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t("Copied!") : _t("Failed to copy"),
});
// Drop a reference to this close handler for componentWillUnmount
this.closeCopiedTooltip = target.onmouseleave = close;
};
render() { render() {
let spinner = null; let spinner = null;
if (this.state.busy) { if (this.state.busy) {
@ -1409,18 +1392,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
const link = makeUserPermalink(MatrixClientPeg.get().getUserId()); const link = makeUserPermalink(MatrixClientPeg.get().getUserId());
footer = <div className="mx_InviteDialog_footer"> footer = <div className="mx_InviteDialog_footer">
<h3>{ _t("Or send invite link") }</h3> <h3>{ _t("Or send invite link") }</h3>
<div className="mx_InviteDialog_footer_link"> <CopyableText getTextToCopy={() => makeUserPermalink(MatrixClientPeg.get().getUserId())}>
<a href={link} onClick={this.onLinkClick}> <a href={link} onClick={this.onLinkClick}>
{ link } { link }
</a> </a>
<AccessibleTooltipButton </CopyableText>
title={_t("Copy")}
onClick={this.onCopyClick}
className="mx_InviteDialog_footer_link_copy"
>
<div />
</AccessibleTooltipButton>
</div>
</div>; </div>;
} else if (this.props.kind === KIND_INVITE) { } else if (this.props.kind === KIND_INVITE) {
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId); const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);

View File

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { User } from "matrix-js-sdk/src/models/user"; import { User } from "matrix-js-sdk/src/models/user";
import { Group } from "matrix-js-sdk/src/models/group"; import { Group } from "matrix-js-sdk/src/models/group";
@ -26,17 +25,14 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import QRCode from "../elements/QRCode"; import QRCode from "../elements/QRCode";
import { RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import * as ContextMenu from "../../structures/ContextMenu"; import { selectText } from "../../../utils/strings";
import { toRightOf } from "../../structures/ContextMenu";
import { copyPlaintext, selectText } from "../../../utils/strings";
import StyledCheckbox from '../elements/StyledCheckbox'; import StyledCheckbox from '../elements/StyledCheckbox';
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
import { IDialogProps } from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu"; import CopyableText from "../elements/CopyableText";
const socials = [ const socials = [
{ {
@ -78,25 +74,11 @@ interface IState {
@replaceableComponent("views.dialogs.ShareDialog") @replaceableComponent("views.dialogs.ShareDialog")
export default class ShareDialog extends React.PureComponent<IProps, IState> { export default class ShareDialog extends React.PureComponent<IProps, IState> {
static propTypes = {
onFinished: PropTypes.func.isRequired,
target: PropTypes.oneOfType([
PropTypes.instanceOf(Room),
PropTypes.instanceOf(User),
PropTypes.instanceOf(Group),
PropTypes.instanceOf(RoomMember),
PropTypes.instanceOf(MatrixEvent),
]).isRequired,
};
protected closeCopiedTooltip: () => void; protected closeCopiedTooltip: () => void;
constructor(props) { constructor(props) {
super(props); super(props);
this.onCopyClick = this.onCopyClick.bind(this);
this.onLinkSpecificEventCheckboxClick = this.onLinkSpecificEventCheckboxClick.bind(this);
let permalinkCreator: RoomPermalinkCreator = null; let permalinkCreator: RoomPermalinkCreator = null;
if (props.target instanceof Room) { if (props.target instanceof Room) {
permalinkCreator = new RoomPermalinkCreator(props.target); permalinkCreator = new RoomPermalinkCreator(props.target);
@ -115,25 +97,11 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
selectText(e.target); selectText(e.target);
} }
async onCopyClick(e) { private onLinkSpecificEventCheckboxClick = () => {
e.preventDefault();
const target = e.target; // copy target before we go async and React throws it away
const successful = await copyPlaintext(this.getUrl());
const buttonRect = target.getBoundingClientRect();
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
});
// Drop a reference to this close handler for componentWillUnmount
this.closeCopiedTooltip = target.onmouseleave = close;
}
onLinkSpecificEventCheckboxClick() {
this.setState({ this.setState({
linkSpecificEvent: !this.state.linkSpecificEvent, linkSpecificEvent: !this.state.linkSpecificEvent,
}); });
} };
componentWillUnmount() { componentWillUnmount() {
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close // if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
@ -141,7 +109,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
if (this.closeCopiedTooltip) this.closeCopiedTooltip(); if (this.closeCopiedTooltip) this.closeCopiedTooltip();
} }
getUrl() { private getUrl() {
let matrixToUrl; let matrixToUrl;
if (this.props.target instanceof Room) { if (this.props.target instanceof Room) {
@ -238,21 +206,11 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
onFinished={this.props.onFinished} onFinished={this.props.onFinished}
> >
<div className="mx_ShareDialog_content"> <div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_matrixto"> <CopyableText getTextToCopy={() => matrixToUrl}>
<a <a title={_t('Link to room')} href={matrixToUrl} onClick={ShareDialog.onLinkClick}>
title={_t('Link to room')}
href={matrixToUrl}
onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
>
{ matrixToUrl } { matrixToUrl }
</a> </a>
<AccessibleTooltipButton </CopyableText>
title={_t("Copy")}
onClick={this.onCopyClick}
className="mx_ShareDialog_matrixto_copy"
/>
</div>
{ checkbox } { checkbox }
{ qrSocialSection } { qrSocialSection }
</div> </div>