Merge pull request #5387 from matrix-org/t3chguy/fix/13066

Invite / Create DM UX tweaks
This commit is contained in:
Michael Telatynski 2020-11-04 14:01:21 +00:00 committed by GitHub
commit 5a8525dd93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 37 deletions

View file

@ -17,6 +17,7 @@ limitations under the License.
*/ */
@import "./_font-sizes.scss"; @import "./_font-sizes.scss";
@import "./_font-weights.scss";
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic $hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
@ -323,6 +324,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
.mx_Dialog_title { .mx_Dialog_title {
font-size: $font-22px; font-size: $font-22px;
font-weight: $font-semi-bold;
line-height: $font-36px; line-height: $font-36px;
color: $dialog-title-fg-color; color: $dialog-title-fg-color;
} }
@ -348,8 +350,8 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
background-color: $dialog-close-fg-color; background-color: $dialog-close-fg-color;
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
top: 4px; top: 10px;
right: 0px; right: 0;
} }
.mx_Dialog_content { .mx_Dialog_content {

View file

@ -27,37 +27,29 @@ limitations under the License.
padding-left: 8px; padding-left: 8px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
display: flex;
flex-wrap: wrap;
.mx_InviteDialog_userTile { .mx_InviteDialog_userTile {
margin: 6px 6px 0 0;
display: inline-block; display: inline-block;
float: left; min-width: max-content; // prevent manipulation by flexbox
position: relative;
top: 7px;
} }
// Using a textarea for this element, to circumvent autofill // Mostly copied from AddressPickerDialog; overrides bunch of our default text input styles
// Mostly copied from AddressPickerDialog > input[type="text"] {
textarea, margin: 6px 0 !important;
textarea:focus { height: 24px;
height: 34px; line-height: $font-24px;
line-height: $font-34px;
font-size: $font-14px; font-size: $font-14px;
padding-left: 12px; padding-left: 12px;
margin: 0 !important;
border: 0 !important; border: 0 !important;
outline: 0 !important; outline: 0 !important;
resize: none; resize: none;
overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
word-wrap: nowrap; min-width: 40%;
flex: 1 !important;
// Roughly fill about 2/5ths of the available space. This is to try and 'fill' the color: $primary-fg-color !important;
// remaining space after a bunch of pills, but is a bit hacky. Ideally we'd have
// support for "fill remaining width", but traditional tricks don't work with what
// we're pushing into this "field". Flexbox just makes things worse. The theory is
// that users won't need more than about 2/5ths of the input to find the person
// they're looking for.
width: 40%;
} }
} }
@ -148,6 +140,10 @@ limitations under the License.
} }
} }
.mx_InviteDialog_roomTile_nameStack {
display: inline-block;
}
.mx_InviteDialog_roomTile_name { .mx_InviteDialog_roomTile_name {
font-weight: 600; font-weight: 600;
font-size: $font-14px; font-size: $font-14px;

View file

@ -280,11 +280,17 @@ class DMRoomTile extends React.PureComponent {
</span> </span>
); );
const caption = this.props.member.isEmail
? _t("Invite by email")
: this._highlightName(this.props.member.userId);
return ( return (
<div className='mx_InviteDialog_roomTile' onClick={this._onClick}> <div className='mx_InviteDialog_roomTile' onClick={this._onClick}>
{stackedAvatar} {stackedAvatar}
<span className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</span> <span className="mx_InviteDialog_roomTile_nameStack">
<span className='mx_InviteDialog_roomTile_userId'>{this._highlightName(this.props.member.userId)}</span> <div className='mx_InviteDialog_roomTile_name'>{this._highlightName(this.props.member.name)}</div>
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
</span>
{timestamp} {timestamp}
</div> </div>
); );
@ -663,12 +669,21 @@ export default class InviteDialog extends React.PureComponent {
}; };
_onKeyDown = (e) => { _onKeyDown = (e) => {
// when the field is empty and the user hits backspace remove the right-most target if (this.state.busy) return;
if (!e.target.value && !this.state.busy && this.state.targets.length > 0 && e.key === Key.BACKSPACE && const value = e.target.value.trim();
!e.ctrlKey && !e.shiftKey && !e.metaKey const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
) { if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
// when the field is empty and the user hits backspace remove the right-most target
e.preventDefault(); e.preventDefault();
this._removeMember(this.state.targets[this.state.targets.length - 1]); this._removeMember(this.state.targets[this.state.targets.length - 1]);
} else if (value && e.key === Key.ENTER && !hasModifiers) {
// when the user hits enter with something in their field try to convert it
e.preventDefault();
this._convertFilter();
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
e.preventDefault();
this._convertFilter();
} }
}; };
@ -811,6 +826,10 @@ export default class InviteDialog extends React.PureComponent {
filterText = ""; // clear the filter when the user accepts a suggestion filterText = ""; // clear the filter when the user accepts a suggestion
} }
this.setState({targets, filterText}); this.setState({targets, filterText});
if (this._editorRef && this._editorRef.current) {
this._editorRef.current.focus();
}
}; };
_removeMember = (member: Member) => { _removeMember = (member: Member) => {
@ -820,6 +839,10 @@ export default class InviteDialog extends React.PureComponent {
targets.splice(idx, 1); targets.splice(idx, 1);
this.setState({targets}); this.setState({targets});
} }
if (this._editorRef && this._editorRef.current) {
this._editorRef.current.focus();
}
}; };
_onPaste = async (e) => { _onPaste = async (e) => {
@ -829,7 +852,7 @@ export default class InviteDialog extends React.PureComponent {
return; return;
} }
// Prevent the text being pasted into the textarea // Prevent the text being pasted into the input
e.preventDefault(); e.preventDefault();
// Process it as a list of addresses to add instead // Process it as a list of addresses to add instead
@ -1024,8 +1047,8 @@ export default class InviteDialog extends React.PureComponent {
<DMUserTile member={t} onRemove={!this.state.busy && this._removeMember} key={t.userId} /> <DMUserTile member={t} onRemove={!this.state.busy && this._removeMember} key={t.userId} />
)); ));
const input = ( const input = (
<textarea <input
rows={1} type="text"
onKeyDown={this._onKeyDown} onKeyDown={this._onKeyDown}
onChange={this._updateFilter} onChange={this._updateFilter}
value={this.state.filterText} value={this.state.filterText}
@ -1033,6 +1056,7 @@ export default class InviteDialog extends React.PureComponent {
onPaste={this._onPaste} onPaste={this._onPaste}
autoFocus={true} autoFocus={true}
disabled={this.state.busy} disabled={this.state.busy}
autoComplete="off"
/> />
); );
return ( return (
@ -1103,7 +1127,7 @@ export default class InviteDialog extends React.PureComponent {
if (identityServersEnabled) { if (identityServersEnabled) {
helpText = _t( helpText = _t(
"Start a conversation with someone using their name, username (like <userId/>) or email address.", "Start a conversation with someone using their name, email address or username (like <userId/>).",
{}, {},
{userId: () => { {userId: () => {
return ( return (
@ -1158,7 +1182,7 @@ export default class InviteDialog extends React.PureComponent {
if (identityServersEnabled) { if (identityServersEnabled) {
helpText = _t( helpText = _t(
"Invite someone using their name, username (like <userId/>), email address or " + "Invite someone using their name, email address, username (like <userId/>) or " +
"<a>share this room</a>.", "<a>share this room</a>.",
{}, {},
{ {

View file

@ -1724,6 +1724,7 @@
"To continue, use Single Sign On to prove your identity.": "To continue, use Single Sign On to prove your identity.", "To continue, use Single Sign On to prove your identity.": "To continue, use Single Sign On to prove your identity.",
"Confirm to continue": "Confirm to continue", "Confirm to continue": "Confirm to continue",
"Click the button below to confirm your identity.": "Click the button below to confirm your identity.", "Click the button below to confirm your identity.": "Click the button below to confirm your identity.",
"Invite by email": "Invite by email",
"Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s", "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.", "We couldn't create your DM. Please check the users you want to invite and try again.": "We couldn't create your DM. Please check the users you want to invite and try again.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.", "Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
@ -1735,11 +1736,11 @@
"May include members not in %(communityName)s": "May include members not in %(communityName)s", "May include members not in %(communityName)s": "May include members not in %(communityName)s",
"Recently Direct Messaged": "Recently Direct Messaged", "Recently Direct Messaged": "Recently Direct Messaged",
"Direct Messages": "Direct Messages", "Direct Messages": "Direct Messages",
"Start a conversation with someone using their name, username (like <userId/>) or email address.": "Start a conversation with someone using their name, username (like <userId/>) or email address.", "Start a conversation with someone using their name, email address or username (like <userId/>).": "Start a conversation with someone using their name, email address or username (like <userId/>).",
"Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).", "Start a conversation with someone using their name or username (like <userId/>).": "Start a conversation with someone using their name or username (like <userId/>).",
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click <a>here</a>",
"Go": "Go", "Go": "Go",
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.", "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.", "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
"a new master key signature": "a new master key signature", "a new master key signature": "a new master key signature",
"a new cross-signing key signature": "a new cross-signing key signature", "a new cross-signing key signature": "a new cross-signing key signature",

View file

@ -64,7 +64,7 @@ async function createDm(session, invitees) {
const startChatButton = await dmsSublist.$(".mx_RoomSublist_auxButton"); const startChatButton = await dmsSublist.$(".mx_RoomSublist_auxButton");
await startChatButton.click(); await startChatButton.click();
const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea'); const inviteesEditor = await session.query('.mx_InviteDialog_editor input');
for (const target of invitees) { for (const target of invitees) {
await session.replaceInputText(inviteesEditor, target); await session.replaceInputText(inviteesEditor, target);
await session.delay(1000); // give it a moment to figure out a suggestion await session.delay(1000); // give it a moment to figure out a suggestion

View file

@ -31,7 +31,7 @@ module.exports = async function invite(session, userId) {
} }
const inviteButton = await session.query(".mx_MemberList_invite"); const inviteButton = await session.query(".mx_MemberList_invite");
await inviteButton.click(); await inviteButton.click();
const inviteTextArea = await session.query(".mx_InviteDialog_editor textarea"); const inviteTextArea = await session.query(".mx_InviteDialog_editor input");
await inviteTextArea.type(userId); await inviteTextArea.type(userId);
const selectUserItem = await session.query(".mx_InviteDialog_roomTile"); const selectUserItem = await session.query(".mx_InviteDialog_roomTile");
await selectUserItem.click(); await selectUserItem.click();