Merge pull request #5387 from matrix-org/t3chguy/fix/13066
Invite / Create DM UX tweaks
This commit is contained in:
commit
5a8525dd93
6 changed files with 60 additions and 37 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>.",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue