diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index 05cddf2c48..0a5ac9b2bc 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -14,6 +14,35 @@ See the License for the specific language governing permissions and limitations under the License. */ +.mx_InteractiveAuthEntryComponents_emailWrapper { + padding-right: 60px; + position: relative; + margin-top: 32px; + margin-bottom: 32px; + + &::before, &::after { + position: absolute; + width: 116px; + height: 116px; + content: ""; + right: -10px; + } + + &::before { + background-color: rgba(244, 246, 250, 0.91); + border-radius: 50%; + top: -20px; + } + + &::after { + background-image: url('$(res)/img/element-icons/email-prompt.svg'); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + top: -25px; + } +} + .mx_InteractiveAuthEntryComponents_msisdnWrapper { text-align: center; } diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 78e7307bc0..6ea99585d2 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -33,7 +33,6 @@ limitations under the License. div:first-child { font-weight: $font-semi-bold; - margin-bottom: 8px; } .mx_AccessibleButton { @@ -41,6 +40,7 @@ limitations under the License. position: relative; padding: 0 0 0 24px; font-size: inherit; + margin-top: 8px; &::before { content: ''; @@ -53,6 +53,13 @@ limitations under the License. mask-position: center; mask-size: contain; mask-repeat: no-repeat; + } + + &.mx_RoomList_explorePrompt_startChat::before { + mask-image: url('$(res)/img/element-icons/feedback.svg'); + } + + &.mx_RoomList_explorePrompt_explore::before { mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); } } diff --git a/res/img/element-icons/email-prompt.svg b/res/img/element-icons/email-prompt.svg new file mode 100644 index 0000000000..19b8f82449 --- /dev/null +++ b/res/img/element-icons/email-prompt.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 7eb7f5dbb2..06d3fb04e8 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -40,11 +40,11 @@ export function inviteMultipleToRoom(roomId, addrs) { return inviter.invite(addrs).then(states => Promise.resolve({states, inviter})); } -export function showStartChatInviteDialog() { +export function showStartChatInviteDialog(initialText) { // This dialog handles the room creation internally - we don't need to worry about it. const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); Modal.createTrackedDialog( - 'Start DM', '', InviteDialog, {kind: KIND_DM}, + 'Start DM', '', InviteDialog, {kind: KIND_DM, initialText}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 22cd73eff7..b2c94e4a8b 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -653,8 +653,9 @@ export default class MatrixChat extends React.PureComponent { } case Action.ViewRoomDirectory: { const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); - Modal.createTrackedDialog('Room directory', '', RoomDirectory, {}, - 'mx_RoomDirectory_dialogWrapper', false, true); + Modal.createTrackedDialog('Room directory', '', RoomDirectory, { + initialText: payload.initialText, + }, 'mx_RoomDirectory_dialogWrapper', false, true); // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); @@ -677,7 +678,7 @@ export default class MatrixChat extends React.PureComponent { this.chatCreateOrReuse(payload.user_id); break; case 'view_create_chat': - showStartChatInviteDialog(); + showStartChatInviteDialog(payload.initialText || ""); break; case 'view_invite': showRoomInviteDialog(payload.roomId); diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index ece70e3a8f..e3323b05fa 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -44,6 +44,7 @@ function track(action) { export default class RoomDirectory extends React.Component { static propTypes = { + initialText: PropTypes.string, onFinished: PropTypes.func.isRequired, }; @@ -61,7 +62,7 @@ export default class RoomDirectory extends React.Component { error: null, instanceId: undefined, roomServer: MatrixClientPeg.getHomeserverName(), - filterString: null, + filterString: this.props.initialText || "", selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") ? selectedCommunityId : null, @@ -686,6 +687,7 @@ export default class RoomDirectory extends React.Component { onJoinClick={this.onJoinFromSearchClick} placeholder={placeholder} showJoinButton={showJoinButton} + initialText={this.props.initialText} /> {dropdown} ; diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index 526aecddd7..a64e40bc65 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -148,7 +148,7 @@ export default class RoomSearch extends React.PureComponent { onBlur={this.onBlur} onChange={this.onChange} onKeyDown={this.onKeyDown} - placeholder={_t("Search")} + placeholder={_t("Filter")} autoComplete="off" /> ); @@ -164,7 +164,7 @@ export default class RoomSearch extends React.PureComponent { if (this.props.isMinimized) { icon = ( diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 630e04da9c..80bf3b72cd 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -502,6 +502,11 @@ export default class Registration extends React.Component { return null; } + // Hide the server picker once the user is doing UI Auth unless encountered a fatal server error + if (this.state.phase !== PHASE_SERVER_DETAILS && this.state.doingUIAuth && !this.state.serverErrorIsFatal) { + return null; + } + // If we're on a different phase, we only show the server type selector, // which is always shown if we allow custom URLs at all. // (if there's a fatal server error, we need to show the full server @@ -582,17 +587,6 @@ export default class Registration extends React.Component { ; } else if (this.state.flows.length) { - let onEditServerDetailsClick = null; - // If custom URLs are allowed and we haven't selected the Free server type, wire - // up the server details edit link. - if ( - PHASES_ENABLED && - !SdkConfig.get()['disable_custom_urls'] && - this.state.serverType !== ServerType.FREE - ) { - onEditServerDetailsClick = this.onEditServerDetailsClick; - } - return ; } else { + let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { + serverName: this.props.serverConfig.hsName, + }); + if (this.props.serverConfig.hsNameIsDifferent) { + const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip"); + + yourMatrixAccountText = _t('Create your Matrix account on ', {}, { + 'underlinedServerName': () => { + return ; + }, + }); + } + + // If custom URLs are allowed, user is not doing UIA flows and they haven't selected the Free server type, + // wire up the server details edit link. + let editLink = null; + if (PHASES_ENABLED && + !SdkConfig.get()['disable_custom_urls'] && + this.state.serverType !== ServerType.FREE && + !this.state.doingUIAuth + ) { + editLink = ( + + {_t('Change')} + + ); + } + body =

{ _t('Create your account') }

{ errorText } { serverDeadSection } { this.renderServerComponent() } + { this.state.phase !== PHASE_SERVER_DETAILS &&

+ {yourMatrixAccountText} + {editLink} +

} { this.renderRegisterComponent() } { goBack } { signIn } diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index 5cce93f0b8..e2d7d594fa 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -102,6 +102,10 @@ export default class CaptchaForm extends React.Component { console.log("Loaded recaptcha script."); try { this._renderRecaptcha(DIV_ID); + // clear error if re-rendered + this.setState({ + errorText: null, + }); CountlyAnalytics.instance.track("onboarding_grecaptcha_loaded"); } catch (e) { this.setState({ diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index f49e6959fb..6628ca7120 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -421,12 +421,12 @@ export class EmailIdentityAuthEntry extends React.Component { return ; } else { return ( -
-

{ _t("An email has been sent to %(emailAddress)s", - { emailAddress: (sub) => { this.props.inputs.emailAddress } }, +

+

{ _t("A confirmation email has been sent to %(emailAddress)s", + { emailAddress: (sub) => { this.props.inputs.emailAddress } }, ) }

-

{ _t("Please check your email to continue registration.") }

+

{ _t("Open the link in the email to continue registration.") }

); } diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 419443984a..70c1017427 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -51,7 +51,6 @@ export default class RegistrationForm extends React.Component { defaultUsername: PropTypes.string, defaultPassword: PropTypes.string, onRegisterClick: PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise - onEditServerDetailsClick: PropTypes.func, flows: PropTypes.arrayOf(PropTypes.object).isRequired, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, canSubmit: PropTypes.bool, @@ -461,7 +460,7 @@ export default class RegistrationForm extends React.Component { ref={field => this[FIELD_PASSWORD_CONFIRM] = field} type="password" autoComplete="new-password" - label={_t("Confirm")} + label={_t("Confirm password")} value={this.state.passwordConfirm} onChange={this.onPasswordConfirmChange} onValidate={this.onPasswordConfirmValidate} @@ -513,33 +512,6 @@ export default class RegistrationForm extends React.Component { } render() { - let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { - serverName: this.props.serverConfig.hsName, - }); - if (this.props.serverConfig.hsNameIsDifferent) { - const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip"); - - yourMatrixAccountText = _t('Create your Matrix account on ', {}, { - 'underlinedServerName': () => { - return ; - }, - }); - } - - let editLink = null; - if (this.props.onEditServerDetailsClick) { - editLink = - {_t('Change')} - ; - } - const registerButton = ( ); @@ -575,10 +547,6 @@ export default class RegistrationForm extends React.Component { return (
-

- {yourMatrixAccountText} - {editLink} -

{this.renderUsername()} diff --git a/src/components/views/dialogs/InviteDialog.js b/src/components/views/dialogs/InviteDialog.js index e29ef143f2..c039c191c5 100644 --- a/src/components/views/dialogs/InviteDialog.js +++ b/src/components/views/dialogs/InviteDialog.js @@ -309,10 +309,14 @@ export default class InviteDialog extends React.PureComponent { // The room ID this dialog is for. Only required for KIND_INVITE. roomId: PropTypes.string, + + // Initial value to populate the filter with + initialText: PropTypes.string, }; static defaultProps = { kind: KIND_DM, + initialText: "", }; _debounceTimer: number = null; @@ -339,7 +343,7 @@ export default class InviteDialog extends React.PureComponent { this.state = { targets: [], // array of Member objects (see interface above) - filterText: "", + filterText: this.props.initialText, recents: InviteDialog.buildRecents(alreadyInvited), numRecentsShown: INITIAL_ROOMS_SHOWN, suggestions: this._buildSuggestions(alreadyInvited), @@ -357,6 +361,12 @@ export default class InviteDialog extends React.PureComponent { this._editorRef = createRef(); } + componentDidMount() { + if (this.props.initialText) { + this._updateSuggestions(this.props.initialText); + } + } + static buildRecents(excludedTargetIds: Set): {userId: string, user: RoomMember, lastActive: number} { const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room @@ -693,6 +703,115 @@ export default class InviteDialog extends React.PureComponent { } }; + _updateSuggestions = async (term) => { + MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { + if (term !== this.state.filterText) { + // Discard the results - we were probably too slow on the server-side to make + // these results useful. This is a race we want to avoid because we could overwrite + // more accurate results. + return; + } + + if (!r.results) r.results = []; + + // While we're here, try and autocomplete a search result for the mxid itself + // if there's no matches (and the input looks like a mxid). + if (term[0] === '@' && term.indexOf(':') > 1) { + try { + const profile = await MatrixClientPeg.get().getProfileInfo(term); + if (profile) { + // If we have a profile, we have enough information to assume that + // the mxid can be invited - add it to the list. We stick it at the + // top so it is most obviously presented to the user. + r.results.splice(0, 0, { + user_id: term, + display_name: profile['displayname'], + avatar_url: profile['avatar_url'], + }); + } + } catch (e) { + console.warn("Non-fatal error trying to make an invite for a user ID"); + console.warn(e); + + // Add a result anyways, just without a profile. We stick it at the + // top so it is most obviously presented to the user. + r.results.splice(0, 0, { + user_id: term, + display_name: term, + avatar_url: null, + }); + } + } + + this.setState({ + serverResultsMixin: r.results.map(u => ({ + userId: u.user_id, + user: new DirectoryMember(u), + })), + }); + }).catch(e => { + console.error("Error searching user directory:"); + console.error(e); + this.setState({serverResultsMixin: []}); // clear results because it's moderately fatal + }); + + // Whenever we search the directory, also try to search the identity server. It's + // all debounced the same anyways. + if (!this.state.canUseIdentityServer) { + // The user doesn't have an identity server set - warn them of that. + this.setState({tryingIdentityServer: true}); + return; + } + if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { + // Start off by suggesting the plain email while we try and resolve it + // to a real account. + this.setState({ + // per above: the userId is a lie here - it's just a regular identifier + threepidResultsMixin: [{user: new ThreepidMember(term), userId: term}], + }); + try { + const authClient = new IdentityAuthClient(); + const token = await authClient.getAccessToken(); + if (term !== this.state.filterText) return; // abandon hope + + const lookup = await MatrixClientPeg.get().lookupThreePid( + 'email', + term, + undefined, // callback + token, + ); + if (term !== this.state.filterText) return; // abandon hope + + if (!lookup || !lookup.mxid) { + // We weren't able to find anyone - we're already suggesting the plain email + // as an alternative, so do nothing. + return; + } + + // We append the user suggestion to give the user an option to click + // the email anyways, and so we don't cause things to jump around. In + // theory, the user would see the user pop up and think "ah yes, that + // person!" + const profile = await MatrixClientPeg.get().getProfileInfo(lookup.mxid); + if (term !== this.state.filterText || !profile) return; // abandon hope + this.setState({ + threepidResultsMixin: [...this.state.threepidResultsMixin, { + user: new DirectoryMember({ + user_id: lookup.mxid, + display_name: profile.displayname, + avatar_url: profile.avatar_url, + }), + userId: lookup.mxid, + }], + }); + } catch (e) { + console.error("Error searching identity server:"); + console.error(e); + this.setState({threepidResultsMixin: []}); // clear results because it's moderately fatal + } + } + }; + _updateFilter = (e) => { const term = e.target.value; this.setState({filterText: term}); @@ -703,113 +822,8 @@ export default class InviteDialog extends React.PureComponent { if (this._debounceTimer) { clearTimeout(this._debounceTimer); } - this._debounceTimer = setTimeout(async () => { - MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { - if (term !== this.state.filterText) { - // Discard the results - we were probably too slow on the server-side to make - // these results useful. This is a race we want to avoid because we could overwrite - // more accurate results. - return; - } - - if (!r.results) r.results = []; - - // While we're here, try and autocomplete a search result for the mxid itself - // if there's no matches (and the input looks like a mxid). - if (term[0] === '@' && term.indexOf(':') > 1) { - try { - const profile = await MatrixClientPeg.get().getProfileInfo(term); - if (profile) { - // If we have a profile, we have enough information to assume that - // the mxid can be invited - add it to the list. We stick it at the - // top so it is most obviously presented to the user. - r.results.splice(0, 0, { - user_id: term, - display_name: profile['displayname'], - avatar_url: profile['avatar_url'], - }); - } - } catch (e) { - console.warn("Non-fatal error trying to make an invite for a user ID"); - console.warn(e); - - // Add a result anyways, just without a profile. We stick it at the - // top so it is most obviously presented to the user. - r.results.splice(0, 0, { - user_id: term, - display_name: term, - avatar_url: null, - }); - } - } - - this.setState({ - serverResultsMixin: r.results.map(u => ({ - userId: u.user_id, - user: new DirectoryMember(u), - })), - }); - }).catch(e => { - console.error("Error searching user directory:"); - console.error(e); - this.setState({serverResultsMixin: []}); // clear results because it's moderately fatal - }); - - // Whenever we search the directory, also try to search the identity server. It's - // all debounced the same anyways. - if (!this.state.canUseIdentityServer) { - // The user doesn't have an identity server set - warn them of that. - this.setState({tryingIdentityServer: true}); - return; - } - if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) { - // Start off by suggesting the plain email while we try and resolve it - // to a real account. - this.setState({ - // per above: the userId is a lie here - it's just a regular identifier - threepidResultsMixin: [{user: new ThreepidMember(term), userId: term}], - }); - try { - const authClient = new IdentityAuthClient(); - const token = await authClient.getAccessToken(); - if (term !== this.state.filterText) return; // abandon hope - - const lookup = await MatrixClientPeg.get().lookupThreePid( - 'email', - term, - undefined, // callback - token, - ); - if (term !== this.state.filterText) return; // abandon hope - - if (!lookup || !lookup.mxid) { - // We weren't able to find anyone - we're already suggesting the plain email - // as an alternative, so do nothing. - return; - } - - // We append the user suggestion to give the user an option to click - // the email anyways, and so we don't cause things to jump around. In - // theory, the user would see the user pop up and think "ah yes, that - // person!" - const profile = await MatrixClientPeg.get().getProfileInfo(lookup.mxid); - if (term !== this.state.filterText || !profile) return; // abandon hope - this.setState({ - threepidResultsMixin: [...this.state.threepidResultsMixin, { - user: new DirectoryMember({ - user_id: lookup.mxid, - display_name: profile.displayname, - avatar_url: profile.avatar_url, - }), - userId: lookup.mxid, - }], - }); - } catch (e) { - console.error("Error searching identity server:"); - console.error(e); - this.setState({threepidResultsMixin: []}); // clear results because it's moderately fatal - } - } + this._debounceTimer = setTimeout(() => { + this._updateSuggestions(term); }, 150); // 150ms debounce (human reaction time + some) }; diff --git a/src/components/views/elements/DirectorySearchBox.js b/src/components/views/elements/DirectorySearchBox.js index c2e8e4fd68..644b69417b 100644 --- a/src/components/views/elements/DirectorySearchBox.js +++ b/src/components/views/elements/DirectorySearchBox.js @@ -20,8 +20,8 @@ import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; export default class DirectorySearchBox extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this._collectInput = this._collectInput.bind(this); this._onClearClick = this._onClearClick.bind(this); this._onChange = this._onChange.bind(this); @@ -31,7 +31,7 @@ export default class DirectorySearchBox extends React.Component { this.input = null; this.state = { - value: '', + value: this.props.initialText || '', }; } @@ -90,15 +90,20 @@ export default class DirectorySearchBox extends React.Component { } return
- - { joinButton } - -
; + + { joinButton } + +
; } } @@ -109,4 +114,5 @@ DirectorySearchBox.propTypes = { onJoinClick: PropTypes.func, placeholder: PropTypes.string, showJoinButton: PropTypes.bool, + initialText: PropTypes.string, }; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index d952c137cd..6e677f2b01 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -58,6 +58,7 @@ interface IProps { interface IState { sublists: ITagMap; + isNameFiltering: boolean; } const TAG_ORDER: TagID[] = [ @@ -183,6 +184,7 @@ export default class RoomList extends React.PureComponent { this.state = { sublists: {}, + isNameFiltering: !!RoomListStore.instance.getFirstNameFilterCondition(), }; this.dispatcherRef = defaultDispatcher.register(this.onAction); @@ -253,7 +255,8 @@ export default class RoomList extends React.PureComponent { return CustomRoomTagStore.getTags()[t]; }); - let doUpdate = arrayHasDiff(previousListIds, newListIds); + const isNameFiltering = !!RoomListStore.instance.getFirstNameFilterCondition(); + let doUpdate = this.state.isNameFiltering !== isNameFiltering || arrayHasDiff(previousListIds, newListIds); if (!doUpdate) { // so we didn't have the visible sublists change, but did the contents of those // sublists change significantly enough to break the sticky headers? Probably, so @@ -275,14 +278,20 @@ export default class RoomList extends React.PureComponent { const newSublists = objectWithOnly(newLists, newListIds); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - this.setState({sublists}, () => { + this.setState({sublists, isNameFiltering}, () => { this.props.onResize(); }); } }; + private onStartChat = () => { + const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; + dis.dispatch({ action: "view_create_chat", initialText }); + }; + private onExplore = () => { - dis.fire(Action.ViewRoomDirectory); + const initialText = RoomListStore.instance.getFirstNameFilterCondition()?.search; + dis.dispatch({ action: Action.ViewRoomDirectory, initialText }); }; private renderCommunityInvites(): TemporaryTile[] { @@ -332,8 +341,9 @@ export default class RoomList extends React.PureComponent { return p; }, [] as TagID[]); - // show a skeleton UI if the user is in no rooms - const showSkeleton = Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length); + // show a skeleton UI if the user is in no rooms and they are not filtering + const showSkeleton = !this.state.isNameFiltering && + Object.values(RoomListStore.instance.unfilteredLists).every(list => !list?.length); for (const orderedTagId of tagOrder) { const orderedRooms = this.state.sublists[orderedTagId] || []; @@ -370,10 +380,21 @@ export default class RoomList extends React.PureComponent { public render() { let explorePrompt: JSX.Element; if (!this.props.isMinimized) { - if (RoomListStore.instance.getFirstNameFilterCondition()) { + if (this.state.isNameFiltering) { explorePrompt =
{_t("Can't see what you’re looking for?")}
- + + {_t("Start a new chat")} + + {_t("Explore all public rooms")}
; @@ -385,7 +406,18 @@ export default class RoomList extends React.PureComponent { if (unfilteredRooms.length < 1 && unfilteredHistorical < 1) { explorePrompt =
{_t("Use the + to make a new room or explore existing ones below")}
- + + {_t("Start a new chat")} + + {_t("Explore all public rooms")}
; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 40356b11d5..1f9a25cac6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1390,6 +1390,7 @@ "Historical": "Historical", "Custom Tag": "Custom Tag", "Can't see what you’re looking for?": "Can't see what you’re looking for?", + "Start a new chat": "Start a new chat", "Explore all public rooms": "Explore all public rooms", "Use the + to make a new room or explore existing ones below": "Use the + to make a new room or explore existing ones below", "%(count)s results|other": "%(count)s results", @@ -2208,8 +2209,8 @@ "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", - "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", - "Please check your email to continue registration.": "Please check your email to continue registration.", + "A confirmation email has been sent to %(emailAddress)s": "A confirmation email has been sent to %(emailAddress)s", + "Open the link in the email to continue registration.": "Open the link in the email to continue registration.", "Token incorrect": "Token incorrect", "A text message has been sent to %(msisdn)s": "A text message has been sent to %(msisdn)s", "Please enter the code it contains:": "Please enter the code it contains:", @@ -2246,8 +2247,6 @@ "Enter username": "Enter username", "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", - "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Create your Matrix account on ": "Create your Matrix account on ", "Register": "Register", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", @@ -2370,8 +2369,9 @@ "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", "Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s", + "Filter": "Filter", "Clear filter": "Clear filter", - "Search rooms": "Search rooms", + "Filter rooms and people": "Filter rooms and people", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", @@ -2470,6 +2470,8 @@ "Log in to your new account.": "Log in to your new account.", "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", + "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", + "Create your Matrix account on ": "Create your Matrix account on ", "Create your account": "Create your account", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key",