Merge pull request #5383 from matrix-org/t3chguy/fix/15604

Tweaks to toasts and post-registration landing
pull/21833/head
Michael Telatynski 2020-11-04 13:59:51 +00:00 committed by GitHub
commit 5c9acc364f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 284 additions and 60 deletions

View File

@ -50,6 +50,44 @@ limitations under the License.
color: $muted-fg-color;
}
.mx_HomePage_userAvatar {
position: relative;
width: min-content;
margin: 0 auto;
&::before, &::after {
content: '';
position: absolute;
height: 26px;
width: 26px;
right: -6px;
bottom: -6px;
}
&::before {
background-color: $primary-bg-color;
border-radius: 50%;
z-index: 1;
}
&::after {
background-color: $secondary-fg-color;
mask-position: center;
mask-repeat: no-repeat;
mask-image: url('$(res)/img/element-icons/camera.svg');
mask-size: 16px;
z-index: 2;
}
&.mx_HomePage_userAvatar_busy::after {
background: url("$(res)/img/spinner.gif") no-repeat center;
background-size: 80%;
mask: unset;
}
}
.mx_HomePage_default_buttons {
margin: 80px auto 0;
width: fit-content;
@ -57,49 +95,43 @@ limitations under the License.
.mx_AccessibleButton {
padding: 73px 8px 15px; // top: 20px top padding + 40px icon + 13px margin
width: 104px; // 120px - 2* 8px
margin: 0 39px; // 55px - 2* 8px
width: 160px;
height: 132px;
margin: 0 20px;
position: relative;
display: inline-block;
border-radius: 8px;
vertical-align: top;
word-break: break-word;
box-sizing: border-box;
font-weight: 600;
font-size: $font-15px;
line-height: $font-20px;
color: $muted-fg-color;
&:hover {
color: $accent-color;
background: rgba($accent-color, 0.06);
&::before {
background-color: $accent-color;
}
}
color: #fff; // on all themes
background-color: $accent-color;
&::before {
top: 20px;
left: 40px; // (120px-40px)/2
left: 60px; // (160px-40px)/2
width: 40px;
height: 40px;
content: '';
position: absolute;
background-color: $muted-fg-color;
background-color: #fff; // on all themes
mask-repeat: no-repeat;
mask-size: contain;
}
&.mx_HomePage_button_sendDm::before {
mask-image: url('$(res)/img/feather-customised/message-circle.svg');
mask-image: url('$(res)/img/element-icons/feedback.svg');
}
&.mx_HomePage_button_explore::before {
mask-image: url('$(res)/img/feather-customised/explore.svg');
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
}
&.mx_HomePage_button_createGroup::before {
mask-image: url('$(res)/img/feather-customised/group.svg');
mask-image: url('$(res)/img/element-icons/community-members.svg');
}
}
}

View File

@ -383,3 +383,22 @@ limitations under the License.
.mx_RoomSublist_addRoomTooltip {
margin-top: -3px;
}
.mx_RoomSublist_skeletonUI {
position: relative;
margin-left: 4px;
height: 288px;
&::before {
background: $roomsublist-skeleton-ui-bg;
width: 100%;
height: 100%;
content: '';
position: absolute;
mask-repeat: repeat-y;
mask-size: auto 48px;
mask-image: url('$(res)/img/element-icons/roomlist/skeleton-ui.svg');
}
}

View File

@ -0,0 +1,10 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4896 2.5C9.04778 2.5 7.827 3.52171 7.54879 4.90624C7.50711 5.11367 7.42679 5.31408 7.28726 5.47312L6.6851 6.15949C6.49523 6.37591 6.22129 6.5 5.93338 6.5H2.75C1.64543 6.5 0.75 7.39543 0.75 8.5V19.5C0.75 20.6046 1.64543 21.5 2.75 21.5H22.75C23.8546 21.5 24.75 20.6046 24.75 19.5V8.5C24.75 7.39543 23.8546 6.5 22.75 6.5H19.5666C19.2787 6.5 19.0048 6.37591 18.8149 6.15949L18.2127 5.47312C18.0732 5.31408 17.9929 5.11366 17.9512 4.90623C17.673 3.5217 16.4522 2.5 15.0104 2.5H10.4896ZM16.75 13.5C16.75 15.7091 14.9591 17.5 12.75 17.5C10.5409 17.5 8.75 15.7091 8.75 13.5C8.75 11.2909 10.5409 9.5 12.75 9.5C14.9591 9.5 16.75 11.2909 16.75 13.5ZM3.25 5C2.97386 5 2.75 5.22386 2.75 5.5C2.75 5.77614 2.97386 6 3.25 6H5.25C5.52614 6 5.75 5.77614 5.75 5.5C5.75 5.22386 5.52614 5 5.25 5H3.25Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="24" height="24" fill="white" transform="translate(0.75)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,5 @@
<svg width="228" height="48" viewBox="0 0 228 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#D4D4D4"/>
<rect x="39" width="189" height="12" rx="6" fill="#D4D4D4"/>
<rect x="39" y="20" width="143" height="12" rx="6" fill="#D4D4D4"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@ -117,6 +117,7 @@ $roomlist-filter-active-bg-color: $bg-color;
$roomlist-bg-color: rgba(33, 38, 44, 0.90);
$roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View File

@ -114,6 +114,7 @@ $roomlist-filter-active-bg-color: $roomlist-button-bg-color;
$roomlist-bg-color: $header-panel-bg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View File

@ -181,6 +181,7 @@ $roomlist-filter-active-bg-color: $roomlist-button-bg-color;
$roomlist-bg-color: $header-panel-bg-color;
$roomlist-header-color: $primary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View File

@ -175,6 +175,7 @@ $roomlist-filter-active-bg-color: #ffffff;
$roomlist-bg-color: rgba(245, 245, 245, 0.90);
$roomlist-header-color: $tertiary-fg-color;
$roomsublist-divider-color: $primary-fg-color;
$roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%);
$groupFilterPanel-divider-color: $roomlist-header-color;

View File

@ -24,6 +24,7 @@ import {ActionPayload} from "./dispatcher/payloads";
import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload";
import {Action} from "./dispatcher/actions";
import {hideToast as hideUpdateToast} from "./toasts/UpdateToast";
import {MatrixClientPeg} from "./MatrixClientPeg";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
@ -105,6 +106,9 @@ export default abstract class BasePlatform {
* @param newVersion the version string to check
*/
protected shouldShowUpdate(newVersion: string): boolean {
// If the user registered on this client in the last 24 hours then do not show them the update toast
if (MatrixClientPeg.userRegisteredWithinLastHours(24)) return false;
try {
const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY));
return newVersion !== version || Date.now() > deferUntil;

View File

@ -100,6 +100,12 @@ export interface IMatrixClientPeg {
*/
currentUserIsJustRegistered(): boolean;
/**
* If the current user has been registered by this device then this
* returns a boolean of whether it was within the last N hours given.
*/
userRegisteredWithinLastHours(hours: number): boolean;
/**
* Replace this MatrixClientPeg's client with a client instance that has
* homeserver / identity server URLs and active credentials
@ -150,6 +156,9 @@ class _MatrixClientPeg implements IMatrixClientPeg {
public setJustRegisteredUserId(uid: string): void {
this.justRegisteredUserId = uid;
if (uid) {
window.localStorage.setItem("mx_registration_time", String(new Date().getTime()));
}
}
public currentUserIsJustRegistered(): boolean {
@ -159,6 +168,15 @@ class _MatrixClientPeg implements IMatrixClientPeg {
);
}
public userRegisteredWithinLastHours(hours: number): boolean {
try {
const date = new Date(window.localStorage.getItem("mx_registration_time"));
return ((new Date().getTime() - date.getTime()) / 36e5) <= hours;
} catch (e) {
return false;
}
}
public replaceUsingCreds(creds: IMatrixClientCreds): void {
this.currentClientCreds = creds;
this.createClient(creds);

View File

@ -15,20 +15,105 @@ limitations under the License.
*/
import * as React from "react";
import {useContext, useRef, useState} from "react";
import AutoHideScrollbar from './AutoHideScrollbar';
import { getHomePageUrl } from "../../utils/pages";
import { _t } from "../../languageHandler";
import {getHomePageUrl} from "../../utils/pages";
import {_t} from "../../languageHandler";
import SdkConfig from "../../SdkConfig";
import * as sdk from "../../index";
import dis from "../../dispatcher/dispatcher";
import { Action } from "../../dispatcher/actions";
import {Action} from "../../dispatcher/actions";
import {Transition} from "react-transition-group";
import BaseAvatar from "../views/avatars/BaseAvatar";
import {OwnProfileStore} from "../../stores/OwnProfileStore";
import AccessibleButton from "../views/elements/AccessibleButton";
import Tooltip from "../views/elements/Tooltip";
import {UPDATE_EVENT} from "../../stores/AsyncStore";
import {useEventEmitter} from "../../hooks/useEventEmitter";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import classNames from "classnames";
import {ENTERING} from "react-transition-group/Transition";
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
const onClickExplore = () => dis.fire(Action.ViewRoomDirectory);
const onClickNewRoom = () => dis.dispatch({action: 'view_create_room'});
const HomePage = () => {
interface IProps {
justRegistered?: boolean;
}
const avatarSize = 52;
const getOwnProfile = (userId: string) => ({
displayName: OwnProfileStore.instance.displayName || userId,
avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(avatarSize),
});
const UserWelcomeTop = () => {
const cli = useContext(MatrixClientContext);
const userId = cli.getUserId();
const [ownProfile, setOwnProfile] = useState(getOwnProfile(userId));
useEventEmitter(OwnProfileStore.instance, UPDATE_EVENT, () => {
setOwnProfile(getOwnProfile(userId));
});
const [busy, setBusy] = useState(false);
const uploadRef = useRef<HTMLInputElement>();
return <div>
<input
type="file"
ref={uploadRef}
className="mx_ProfileSettings_avatarUpload"
onChange={async (ev) => {
if (!ev.target.files?.length) return;
setBusy(true);
const file = ev.target.files[0];
const uri = await cli.uploadContent(file);
await cli.setAvatarUrl(uri);
setBusy(false);
}}
accept="image/*"
/>
<AccessibleButton
className={classNames("mx_HomePage_userAvatar", {
mx_HomePage_userAvatar_busy: busy,
})}
disabled={busy}
onClick={() => {
uploadRef.current.click();
}}
>
<BaseAvatar
idName={userId}
name={ownProfile.displayName}
url={ownProfile.avatarUrl}
width={avatarSize}
height={avatarSize}
resizeMethod="crop"
/>
<Transition appear in timeout={3000}>
{state => (
<Tooltip
label={ownProfile.avatarUrl || busy
? _t("Great, that'll help people know it's you")
: _t("Add a photo so people know it's you.")}
visible={state !== ENTERING}
forceOnRight
/>
)}
</Transition>
</AccessibleButton>
<h1>{ _t("Welcome %(name)s", { name: ownProfile.displayName }) }</h1>
<h4>{ _t("Now, lets help you get started") }</h4>
</div>;
};
const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
const config = SdkConfig.get();
const pageUrl = getHomePageUrl(config);
@ -37,18 +122,27 @@ const HomePage = () => {
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
}
const brandingConfig = config.branding;
let logoUrl = "themes/element/img/logos/element-logo.svg";
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
logoUrl = brandingConfig.authHeaderLogoUrl;
let introSection;
if (justRegistered) {
introSection = <UserWelcomeTop />;
} else {
const brandingConfig = config.branding;
let logoUrl = "themes/element/img/logos/element-logo.svg";
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
logoUrl = brandingConfig.authHeaderLogoUrl;
}
introSection = <React.Fragment>
<img src={logoUrl} alt={config.brand} />
<h1>{ _t("Welcome to %(appName)s", { appName: config.brand }) }</h1>
<h4>{ _t("Liberate your communication") }</h4>
</React.Fragment>;
}
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
return <AutoHideScrollbar className="mx_HomePage mx_HomePage_default">
<div className="mx_HomePage_default_wrapper">
<img src={logoUrl} alt={config.brand || "Element"} />
<h1>{ _t("Welcome to %(appName)s", { appName: config.brand || "Element" }) }</h1>
<h4>{ _t("Liberate your communication") }</h4>
{ introSection }
<div className="mx_HomePage_default_buttons">
<AccessibleButton onClick={onClickSendDm} className="mx_HomePage_button_sendDm">
{ _t("Send a Direct Message") }

View File

@ -88,6 +88,7 @@ interface IProps {
currentUserId?: string;
currentGroupId?: string;
currentGroupIsNew?: boolean;
justRegistered?: boolean;
}
interface IUsageLimit {
@ -573,7 +574,7 @@ class LoggedInView extends React.Component<IProps, IState> {
break;
case PageTypes.HomePage:
pageElement = <HomePage />;
pageElement = <HomePage justRegistered={this.props.justRegistered} />;
break;
case PageTypes.UserView:

View File

@ -62,7 +62,7 @@ import DMRoomMap from '../../utils/DMRoomMap';
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { FontWatcher } from '../../settings/watchers/FontWatcher';
import { storeRoomAliasInCache } from '../../RoomAliasCache';
import { defer, IDeferred } from "../../utils/promise";
import { defer, IDeferred, sleep } from "../../utils/promise";
import ToastStore from "../../stores/ToastStore";
import * as StorageManager from "../../utils/StorageManager";
import type LoggedInViewType from "./LoggedInView";
@ -201,6 +201,7 @@ interface IState {
roomOobData?: object;
viaServers?: string[];
pendingInitialSync?: boolean;
justRegistered?: boolean;
}
export default class MatrixChat extends React.PureComponent<IProps, IState> {
@ -479,6 +480,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
const newState = {
currentUserId: null,
justRegistered: false,
};
Object.assign(newState, state);
this.setState(newState);
@ -669,7 +671,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.viewWelcome();
break;
case 'view_home_page':
this.viewHome();
this.viewHome(payload.justRegistered);
break;
case 'view_start_chat_or_reuse':
this.chatCreateOrReuse(payload.user_id);
@ -953,10 +955,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.themeWatcher.recheck();
}
private viewHome() {
private viewHome(justRegistered = false) {
// The home page requires the "logged in" view, so we'll set that.
this.setStateForNewView({
view: Views.LOGGED_IN,
justRegistered,
});
this.setPage(PageTypes.HomePage);
this.notifyNewScreen('home');
@ -1190,7 +1193,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (welcomeUserRoom === null) {
// We didn't redirect to the welcome user room, so show
// the homepage.
dis.dispatch({action: 'view_home_page'});
dis.dispatch({action: 'view_home_page', justRegistered: true});
}
} else if (ThreepidInviteStore.instance.pickBestInvite()) {
// The user has a 3pid invite pending - show them that
@ -1203,7 +1206,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} else {
// The user has just logged in after registering,
// so show the homepage.
dis.dispatch({action: 'view_home_page'});
dis.dispatch({action: 'view_home_page', justRegistered: true});
}
} else {
this.showScreenAfterLogin();
@ -1211,6 +1214,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
StorageManager.tryPersistStorage();
// defer the following actions by 30 seconds to not throw them at the user immediately
await sleep(30);
if (SettingsStore.getValue("showCookieBar") &&
(Analytics.canEnable() || CountlyAnalytics.instance.canEnable())
) {
@ -1343,8 +1348,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.firstSyncComplete = true;
this.firstSyncPromise.resolve();
if (Notifier.shouldShowPrompt()) {
showNotificationsToast();
if (Notifier.shouldShowPrompt() && !MatrixClientPeg.userRegisteredWithinLastHours(24)) {
showNotificationsToast(false);
}
dis.fire(Action.FocusComposer);

View File

@ -74,6 +74,8 @@ import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call";
import WidgetStore from "../../stores/WidgetStore";
import {UPDATE_EVENT} from "../../stores/AsyncStore";
import Notifier from "../../Notifier";
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -1050,6 +1052,11 @@ export default class RoomView extends React.Component<IProps, IState> {
let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
this.setState({isAlone: joinedOrInvitedMemberCount === 1});
// if they are not alone additionally prompt the user about notifications so they don't miss replies
if (joinedOrInvitedMemberCount > 1 && Notifier.shouldShowPrompt()) {
showNotificationsToast(true);
}
}
private updateDMState() {

View File

@ -332,6 +332,8 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
return p;
}, [] as TagID[]);
const showSkeleton = tagOrder.every(tag => !this.state.sublists[tag]?.length);
for (const orderedTagId of tagOrder) {
const orderedRooms = this.state.sublists[orderedTagId] || [];
const extraTiles = orderedTagId === DefaultTagID.Invite ? this.renderCommunityInvites() : null;
@ -356,6 +358,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
showSkeleton={showSkeleton}
extraBadTilesThatShouldntExist={extraTiles}
/>);
}
@ -365,13 +368,28 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
public render() {
let explorePrompt: JSX.Element;
if (!this.props.isMinimized && RoomListStore.instance.getFirstNameFilterCondition()) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Can't see what youre looking for?")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
if (!this.props.isMinimized) {
if (RoomListStore.instance.getFirstNameFilterCondition()) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Can't see what youre looking for?")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
} else if (Object.values(this.state.sublists).some(list => list.length > 0)) {
const unfilteredLists = RoomListStore.instance.unfilteredLists
const unfilteredRooms = unfilteredLists[DefaultTagID.Untagged] || [];
const unfilteredHistorical = unfilteredLists[DefaultTagID.Archived] || [];
// show a prompt to join/create rooms if the user is in 0 rooms and no historical
if (unfilteredRooms.length < 1 && unfilteredHistorical < 1) {
explorePrompt = <div className="mx_RoomList_explorePrompt">
<div>{_t("Use the + to make a new room or explore existing ones below")}</div>
<AccessibleButton kind="link" onClick={this.onExplore}>
{_t("Explore all public rooms")}
</AccessibleButton>
</div>;
}
}
}
const sublists = this.renderSublists();

View File

@ -71,6 +71,7 @@ interface IProps {
isMinimized: boolean;
tagId: TagID;
onResize: () => void;
showSkeleton?: boolean;
// TODO: Don't use this. It's for community invites, and community invites shouldn't be here.
// You should feel bad if you use this.
@ -877,6 +878,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
</Resizable>
</React.Fragment>
);
} else if (this.props.showSkeleton && this.state.isExpanded) {
content = <div className="mx_RoomSublist_skeletonUI" />;
}
return (

View File

@ -403,10 +403,10 @@
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
"Review": "Review",
"Later": "Later",
"Don't miss a reply": "Don't miss a reply",
"Notifications": "Notifications",
"You are not receiving desktop notifications": "You are not receiving desktop notifications",
"Enable them now": "Enable them now",
"Close": "Close",
"Enable desktop notifications": "Enable desktop notifications",
"Enable": "Enable",
"Your homeserver has exceeded its user limit.": "Your homeserver has exceeded its user limit.",
"Your homeserver has exceeded one of its resource limits.": "Your homeserver has exceeded one of its resource limits.",
"Contact your <a>server admin</a>.": "Contact your <a>server admin</a>.",
@ -424,9 +424,8 @@
"What's new?": "What's new?",
"What's New": "What's New",
"Update": "Update",
"Restart": "Restart",
"Upgrade your %(brand)s": "Upgrade your %(brand)s",
"A new version of %(brand)s is available!": "A new version of %(brand)s is available!",
"Update %(brand)s": "Update %(brand)s",
"New version of %(brand)s is available": "New version of %(brand)s is available",
"Guest": "Guest",
"There was an error joining the room": "There was an error joining the room",
"Sorry, your homeserver is too old to participate in this room.": "Sorry, your homeserver is too old to participate in this room.",
@ -689,7 +688,6 @@
"rooms.": "rooms.",
"Manage": "Manage",
"Securely cache encrypted messages locally for them to appear in search results.": "Securely cache encrypted messages locally for them to appear in search results.",
"Enable": "Enable",
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with <nativeLink>search components added</nativeLink>.",
"%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> for encrypted messages to appear in search results.",
"Connecting to integration manager...": "Connecting to integration manager...",
@ -872,6 +870,7 @@
"Ban list rules - %(roomName)s": "Ban list rules - %(roomName)s",
"Server rules": "Server rules",
"User rules": "User rules",
"Close": "Close",
"You have not ignored anyone.": "You have not ignored anyone.",
"You are currently ignoring:": "You are currently ignoring:",
"You are not subscribed to any lists": "You are not subscribed to any lists",
@ -1132,6 +1131,7 @@
"Custom Tag": "Custom Tag",
"Can't see what youre looking for?": "Can't see what youre looking for?",
"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",
"%(count)s results|one": "%(count)s result",
"This room": "This room",
@ -2053,6 +2053,10 @@
"Community %(groupId)s not found": "Community %(groupId)s not found",
"This homeserver does not support communities": "This homeserver does not support communities",
"Failed to load %(groupId)s": "Failed to load %(groupId)s",
"Great, that'll help people know it's you": "Great, that'll help people know it's you",
"Add a photo so people know it's you.": "Add a photo so people know it's you.",
"Welcome %(name)s": "Welcome %(name)s",
"Now, lets help you get started": "Now, lets help you get started",
"Welcome to %(appName)s": "Welcome to %(appName)s",
"Liberate your communication": "Liberate your communication",
"Send a Direct Message": "Send a Direct Message",

View File

@ -29,15 +29,15 @@ const onReject = () => {
const TOAST_KEY = "desktopnotifications";
export const showToast = () => {
export const showToast = (fromMessageSend: boolean) => {
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
title: _t("Notifications"),
title: fromMessageSend ? _t("Don't miss a reply") : _t("Notifications"),
props: {
description: _t("You are not receiving desktop notifications"),
acceptLabel: _t("Enable them now"),
description: _t("Enable desktop notifications"),
acceptLabel: _t("Enable"),
onAccept,
rejectLabel: _t("Close"),
rejectLabel: _t("Dismiss"),
onReject,
},
component: GenericToast,

View File

@ -74,18 +74,18 @@ export const showToast = (version: string, newVersion: string, releaseNotes?: st
};
} else {
onAccept = installUpdate;
acceptLabel = _t("Restart");
acceptLabel = _t("Update");
}
const brand = SdkConfig.get().brand;
ToastStore.sharedInstance().addOrReplaceToast({
key: TOAST_KEY,
title: _t("Upgrade your %(brand)s", { brand }),
title: _t("Update %(brand)s", { brand }),
props: {
description: _t("A new version of %(brand)s is available!", { brand }),
description: _t("New version of %(brand)s is available", { brand }),
acceptLabel,
onAccept,
rejectLabel: _t("Later"),
rejectLabel: _t("Dismiss"),
onReject,
},
component: GenericToast,