Responding to feedback on the updated visuals (#2549)
* Conversation List Item: timestamp bold only when convo has unread * Preserve the positioning of overlays on re-entry into convo * ConversationListItem: Handle missing and broken thumbnails * Shorten timestamp in left pane for better Android consistency * Update convo last updated if last was expire timer change But not if it was from a sync instead of from you or from a contact. * Make links in quotes the same color as the text * MediaGridItem: Update placeholder icon colors for dark theme * Ensure turning off timer shows 'Timer set to off' in left pane * ConversationListItem: Show unread count in blue circle * Add one pixel margin to blue indicator for text alignment * Ensure replies to voice message can bet sent successfully
This commit is contained in:
parent
60d56cf7e0
commit
643739f65d
13 changed files with 348 additions and 38 deletions
|
@ -1149,6 +1149,17 @@
|
|||
"description":
|
||||
"Brief timestamp for messages sent about one hour ago. Displayed in the conversation list and message bubble."
|
||||
},
|
||||
"hoursAgoShort": {
|
||||
"message": "$hours$ hr",
|
||||
"description":
|
||||
"Even further contracted form of 'X hours ago' which works both for singular and plural, used in the left pane",
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"hoursAgo": {
|
||||
"message": "$hours$ hr ago",
|
||||
"description":
|
||||
|
@ -1160,6 +1171,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"minutesAgoShort": {
|
||||
"message": "$minutes$ min",
|
||||
"description":
|
||||
"Even further contracted form of 'X minutes ago' which works both for singular and plural, used in the left pane",
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
"content": "$1",
|
||||
"example": "10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"minutesAgo": {
|
||||
"message": "$minutes$ min ago",
|
||||
"description":
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
...this.format(),
|
||||
|
||||
lastUpdated: this.get('timestamp'),
|
||||
hasUnread: Boolean(this.get('unreadCount')),
|
||||
unreadCount: this.get('unreadCount') || 0,
|
||||
isSelected: this.isSelected,
|
||||
|
||||
lastMessage: {
|
||||
|
@ -795,7 +795,9 @@
|
|||
|
||||
return {
|
||||
contentType,
|
||||
fileName,
|
||||
// Our protos library complains about this field being undefined, so we
|
||||
// force it to null
|
||||
fileName: fileName || null,
|
||||
thumbnail: thumbnail
|
||||
? {
|
||||
...(await loadAttachmentData(thumbnail)),
|
||||
|
|
|
@ -193,7 +193,7 @@
|
|||
const { expireTimer } = this.get('expirationTimerUpdate');
|
||||
return i18n(
|
||||
'timerSetTo',
|
||||
Whisper.ExpirationTimerOptions.getAbbreviated(expireTimer)
|
||||
Whisper.ExpirationTimerOptions.getAbbreviated(expireTimer || 0)
|
||||
);
|
||||
}
|
||||
if (this.isKeyChange()) {
|
||||
|
|
|
@ -752,6 +752,10 @@
|
|||
},
|
||||
|
||||
focusMessageField() {
|
||||
if (this.panels && this.panels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$messageField.focus();
|
||||
},
|
||||
|
||||
|
@ -1286,6 +1290,7 @@
|
|||
|
||||
if (message) {
|
||||
const quote = await this.model.makeQuote(this.quotedMessage);
|
||||
console.log('DEBUG', { quote });
|
||||
this.quote = quote;
|
||||
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
|
|
|
@ -810,6 +810,10 @@
|
|||
line-height: 18px;
|
||||
color: $color-light-90;
|
||||
|
||||
a {
|
||||
color: $color-light-90;
|
||||
}
|
||||
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
|
@ -1805,6 +1809,7 @@
|
|||
background-color: $color-light-10;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.module-media-grid-item__image {
|
||||
|
@ -1813,6 +1818,18 @@
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-image {
|
||||
@include color-svg('../images/image.svg', $color-light-35);
|
||||
}
|
||||
|
||||
.module-media-grid-item__image-container {
|
||||
height: 94px;
|
||||
width: 94px;
|
||||
|
@ -1844,6 +1861,14 @@
|
|||
@include color-svg('../images/play.svg', $color-signal-blue);
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-video {
|
||||
@include color-svg('../images/movie.svg', $color-light-35);
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-generic {
|
||||
@include color-svg('../images/file.svg', $color-light-35);
|
||||
}
|
||||
|
||||
/* Module: Empty State*/
|
||||
|
||||
.module-empty-state {
|
||||
|
@ -1964,7 +1989,6 @@
|
|||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
|
@ -1973,17 +1997,22 @@
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.module-conversation-list-item__header__date--has-unread {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.module-conversation-list-item__message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.module-conversation-list-item__message__text {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
margin-top: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
|
||||
|
@ -1997,6 +2026,23 @@
|
|||
font-weight: 300;
|
||||
}
|
||||
|
||||
.module-conversation-list-item__unread-count {
|
||||
color: $color-white;
|
||||
background-color: $color-signal-blue;
|
||||
text-align: center;
|
||||
|
||||
// For alignment with the message text
|
||||
margin-top: 1px;
|
||||
|
||||
font-size: 10px;
|
||||
margin-left: 5px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
line-height: 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.module-conversation-list-item__message__status-icon {
|
||||
flex-shrink: 0;
|
||||
|
||||
|
|
|
@ -927,6 +927,10 @@ body.dark-theme {
|
|||
|
||||
.module-quote__primary__text {
|
||||
color: $color-dark-05;
|
||||
|
||||
a {
|
||||
color: $color-dark-05;
|
||||
}
|
||||
}
|
||||
|
||||
.module-quote__primary__type-label {
|
||||
|
@ -1275,6 +1279,18 @@ body.dark-theme {
|
|||
background-color: $color-dark-85;
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-image {
|
||||
@include color-svg('../images/image.svg', $color-dark-60);
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-video {
|
||||
@include color-svg('../images/movie.svg', $color-dark-60);
|
||||
}
|
||||
|
||||
.module-media-grid-item__icon-generic {
|
||||
@include color-svg('../images/file.svg', $color-dark-60);
|
||||
}
|
||||
|
||||
// Module: Empty State
|
||||
|
||||
.module-empty-state {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
phoneNumber="(202) 555-0011"
|
||||
name="Mr. Fire🔥"
|
||||
color="green"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Just a second',
|
||||
status: 'read',
|
||||
|
@ -34,16 +35,38 @@
|
|||
#### With unread
|
||||
|
||||
```jsx
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
hasUnread={true}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
status: 'sending',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={4}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={10}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
unreadCount={250}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Selected
|
||||
|
@ -52,6 +75,7 @@
|
|||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
isSelected={true}
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Hey there!',
|
||||
}}
|
||||
|
@ -68,6 +92,7 @@ We don't want Jumbomoji or links.
|
|||
<div>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Download at http://signal.org',
|
||||
}}
|
||||
|
@ -76,6 +101,7 @@ We don't want Jumbomoji or links.
|
|||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: '🔥',
|
||||
}}
|
||||
|
@ -94,6 +120,7 @@ We only show one line.
|
|||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
|
@ -102,6 +129,7 @@ We only show one line.
|
|||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
|
@ -111,6 +139,7 @@ We only show one line.
|
|||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
|
@ -122,6 +151,18 @@ We only show one line.
|
|||
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
unreadCount={8}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
|
@ -131,6 +172,7 @@ We only show one line.
|
|||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Many lines. This is a many-line message.\nLine 2 is really exciting but it shouldn't be seen.\nLine three is even better.\nLine 4, well.",
|
||||
|
@ -142,6 +184,35 @@ We only show one line.
|
|||
</div>
|
||||
```
|
||||
|
||||
#### More narrow
|
||||
|
||||
On platforms that show scrollbars all the time, this is true all the time.
|
||||
|
||||
```jsx
|
||||
<div style={{ width: '280px' }}>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
name="Long contact name. Esquire. The third. And stuff. And more! And more!"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text: 'Normal message',
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
<ConversationListItem
|
||||
phoneNumber="(202) 555-0011"
|
||||
lastUpdated={Date.now() - 5 * 60 * 1000}
|
||||
lastMessage={{
|
||||
text:
|
||||
"Long line. This is a really really really long line. Really really long. Because that's just how it is",
|
||||
}}
|
||||
onClick={() => console.log('onClick')}
|
||||
i18n={util.i18n}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### With various ages
|
||||
|
||||
```jsx
|
||||
|
|
|
@ -14,7 +14,7 @@ interface Props {
|
|||
avatarPath?: string;
|
||||
|
||||
lastUpdated: number;
|
||||
hasUnread: boolean;
|
||||
unreadCount: number;
|
||||
isSelected: boolean;
|
||||
|
||||
lastMessage?: {
|
||||
|
@ -71,7 +71,14 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
}
|
||||
|
||||
public renderHeader() {
|
||||
const { i18n, lastUpdated, name, phoneNumber, profileName } = this.props;
|
||||
const {
|
||||
unreadCount,
|
||||
i18n,
|
||||
lastUpdated,
|
||||
name,
|
||||
phoneNumber,
|
||||
profileName,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className="module-conversation-list-item__header">
|
||||
|
@ -83,7 +90,14 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
<div className="module-conversation-list-item__header__date">
|
||||
<div
|
||||
className={classNames(
|
||||
'module-conversation-list-item__header__date',
|
||||
unreadCount > 0
|
||||
? 'module-conversation-list-item__header__date--has-unread'
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<Timestamp
|
||||
timestamp={lastUpdated}
|
||||
extended={false}
|
||||
|
@ -95,8 +109,22 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
public renderUnread() {
|
||||
const { unreadCount } = this.props;
|
||||
|
||||
if (unreadCount > 0) {
|
||||
return (
|
||||
<div className="module-conversation-list-item__unread-count">
|
||||
{unreadCount}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public renderMessage() {
|
||||
const { lastMessage, hasUnread, i18n } = this.props;
|
||||
const { lastMessage, unreadCount, i18n } = this.props;
|
||||
|
||||
if (!lastMessage) {
|
||||
return null;
|
||||
|
@ -108,7 +136,7 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
<div
|
||||
className={classNames(
|
||||
'module-conversation-list-item__message__text',
|
||||
hasUnread
|
||||
unreadCount > 0
|
||||
? 'module-conversation-list-item__message__text--has-unread'
|
||||
: null
|
||||
)}
|
||||
|
@ -131,12 +159,13 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
)}
|
||||
/>
|
||||
) : null}
|
||||
{this.renderUnread()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { hasUnread, onClick, isSelected } = this.props;
|
||||
const { unreadCount, onClick, isSelected } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -144,7 +173,7 @@ export class ConversationListItem extends React.Component<Props> {
|
|||
onClick={onClick}
|
||||
className={classNames(
|
||||
'module-conversation-list-item',
|
||||
hasUnread ? 'module-conversation-list-item--has-unread' : null,
|
||||
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
|
||||
isSelected ? 'module-conversation-list-item--is-selected' : null
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
## With image
|
||||
#### With image
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
|
@ -14,7 +14,7 @@ const message = {
|
|||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
## With video
|
||||
#### With video
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
|
@ -30,7 +30,69 @@ const message = {
|
|||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
## Without image
|
||||
#### Missing image
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
id: '1',
|
||||
attachments: [
|
||||
{
|
||||
fileName: 'foo.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
],
|
||||
};
|
||||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
#### Missing video
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
id: '1',
|
||||
attachments: [
|
||||
{
|
||||
fileName: 'foo.jpg',
|
||||
contentType: 'video/mp4',
|
||||
},
|
||||
],
|
||||
};
|
||||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
#### Image thumbnail failed to load
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
id: '1',
|
||||
thumbnailObjectUrl: 'nonexistent',
|
||||
attachments: [
|
||||
{
|
||||
fileName: 'foo.jpg',
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
],
|
||||
};
|
||||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
#### Video thumbnail failed to load
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
id: '1',
|
||||
thumbnailObjectUrl: 'nonexistent',
|
||||
attachments: [
|
||||
{
|
||||
fileName: 'foo.jpg',
|
||||
contentType: 'video/mp4',
|
||||
},
|
||||
],
|
||||
};
|
||||
<MediaGridItem i18n={util.i18n} message={message} />;
|
||||
```
|
||||
|
||||
#### Other contentType
|
||||
|
||||
```jsx
|
||||
const message = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {
|
||||
isImageTypeSupported,
|
||||
|
@ -13,14 +14,34 @@ interface Props {
|
|||
i18n: Localizer;
|
||||
}
|
||||
|
||||
export class MediaGridItem extends React.Component<Props> {
|
||||
interface State {
|
||||
imageBroken: boolean;
|
||||
}
|
||||
|
||||
export class MediaGridItem extends React.Component<Props, State> {
|
||||
private onImageErrorBound: () => void;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
imageBroken: false,
|
||||
};
|
||||
|
||||
this.onImageErrorBound = this.onImageError.bind(this);
|
||||
}
|
||||
|
||||
public onImageError() {
|
||||
this.setState({
|
||||
imageBroken: true,
|
||||
});
|
||||
}
|
||||
|
||||
public renderContent() {
|
||||
const { message, i18n } = this.props;
|
||||
const { imageBroken } = this.state;
|
||||
const { attachments } = message;
|
||||
|
||||
if (!message.thumbnailObjectUrl) {
|
||||
return null;
|
||||
}
|
||||
if (!attachments || !attachments.length) {
|
||||
return null;
|
||||
}
|
||||
|
@ -29,20 +50,44 @@ export class MediaGridItem extends React.Component<Props> {
|
|||
const { contentType } = first;
|
||||
|
||||
if (contentType && isImageTypeSupported(contentType)) {
|
||||
if (imageBroken || !message.thumbnailObjectUrl) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-media-grid-item__icon',
|
||||
'module-media-grid-item__icon-image'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
alt={i18n('lightboxImageAlt')}
|
||||
className="module-media-grid-item__image"
|
||||
src={message.thumbnailObjectUrl}
|
||||
onError={this.onImageErrorBound}
|
||||
/>
|
||||
);
|
||||
} else if (contentType && isVideoTypeSupported(contentType)) {
|
||||
if (imageBroken || !message.thumbnailObjectUrl) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-media-grid-item__icon',
|
||||
'module-media-grid-item__icon-video'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="module-media-grid-item__image-container">
|
||||
<img
|
||||
alt={i18n('lightboxImageAlt')}
|
||||
className="module-media-grid-item__image"
|
||||
src={message.thumbnailObjectUrl}
|
||||
onError={this.onImageErrorBound}
|
||||
/>
|
||||
<div className="module-media-grid-item__circle-overlay">
|
||||
<div className="module-media-grid-item__play-overlay" />
|
||||
|
@ -51,7 +96,14 @@ export class MediaGridItem extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'module-media-grid-item__icon',
|
||||
'module-media-grid-item__icon-generic'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -76,7 +76,7 @@ describe('Conversation', () => {
|
|||
});
|
||||
});
|
||||
|
||||
context('for expired message', () => {
|
||||
context('for expire timer update from sync', () => {
|
||||
it('should update message but not timestamp (to prevent bump to top)', () => {
|
||||
const input = {
|
||||
currentLastMessageText: 'I am expired',
|
||||
|
@ -89,7 +89,7 @@ describe('Conversation', () => {
|
|||
timestamp: 666,
|
||||
expirationTimerUpdate: {
|
||||
expireTimer: 111,
|
||||
fromSync: false,
|
||||
fromSync: true,
|
||||
source: '+12223334455',
|
||||
},
|
||||
} as IncomingMessage,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import is from '@sindresorhus/is';
|
||||
import { Message } from './Message';
|
||||
|
||||
interface ConversationLastMessageUpdate {
|
||||
|
@ -28,10 +27,12 @@ export const createLastMessageUpdate = ({
|
|||
};
|
||||
}
|
||||
|
||||
const { type } = lastMessage;
|
||||
const { type, expirationTimerUpdate } = lastMessage;
|
||||
const isVerifiedChangeMessage = type === 'verified-change';
|
||||
const isExpiringMessage = is.object(lastMessage.expirationTimerUpdate);
|
||||
const shouldUpdateTimestamp = !isVerifiedChangeMessage && !isExpiringMessage;
|
||||
const isExpireTimerUpdateFromSync =
|
||||
expirationTimerUpdate && expirationTimerUpdate.fromSync;
|
||||
const shouldUpdateTimestamp =
|
||||
!isVerifiedChangeMessage && !isExpireTimerUpdateFromSync;
|
||||
|
||||
const newTimestamp = shouldUpdateTimestamp
|
||||
? lastMessage.sent_at
|
||||
|
|
|
@ -44,9 +44,13 @@ export function formatRelativeTime(
|
|||
} else if (diff.days() >= 1 || !isToday(timestamp)) {
|
||||
return timestamp.format(formats.d);
|
||||
} else if (diff.hours() >= 1) {
|
||||
return i18n('hoursAgo', [String(diff.hours())]);
|
||||
const key = extended ? 'hoursAgo' : 'hoursAgoShort';
|
||||
|
||||
return i18n(key, [String(diff.hours())]);
|
||||
} else if (diff.minutes() >= 1) {
|
||||
return i18n('minutesAgo', [String(diff.minutes())]);
|
||||
const key = extended ? 'minutesAgo' : 'minutesAgoShort';
|
||||
|
||||
return i18n(key, [String(diff.minutes())]);
|
||||
}
|
||||
|
||||
return i18n('justNow');
|
||||
|
|
Loading…
Reference in a new issue