diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index bb12cf24f..419aac5d1 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -443,6 +443,22 @@
}
}
+.module-message__container--deleted-for-everyone {
+ @include light-theme {
+ color: $color-gray-90;
+ border: 1px solid $color-gray-25;
+ background-color: $color-white;
+ background-image: none;
+ }
+
+ @include dark-theme {
+ color: $color-gray-05;
+ border: 1px solid $color-gray-75;
+ background-color: $color-gray-95;
+ background-image: none;
+ }
+}
+
.module-message__tap-to-view {
margin-top: 2px;
display: flex;
@@ -992,6 +1008,14 @@
.module-message__text--error {
@include font-body-1-italic;
}
+.module-message__text--delete-for-everyone {
+ @include light-theme {
+ color: $color-gray-90;
+ }
+ @include dark-theme {
+ color: $color-gray-05;
+ }
+}
.module-message__metadata {
align-items: center;
@@ -999,6 +1023,7 @@
flex-direction: row;
justify-content: flex-end;
margin-top: 3px;
+ font-style: normal;
&--inline {
float: right;
@@ -1021,6 +1046,15 @@
pointer-events: none;
}
+.module-message__metadata--deleted-for-everyone {
+ @include light-theme {
+ color: $color-gray-60;
+ }
+ @include dark-theme {
+ color: $color-gray-25;
+ }
+}
+
.module-message__metadata__date {
@include font-caption;
user-select: none;
@@ -1054,6 +1088,14 @@
color: $color-white-alpha-80;
}
}
+.module-message__metadata__date--deleted-for-everyone {
+ @include light-theme {
+ color: $color-gray-60;
+ }
+ @include dark-theme {
+ color: $color-gray-25;
+ }
+}
.module-message__metadata__date.module-message__metadata__date--incoming-with-tap-to-view-expired {
color: $color-gray-75;
@@ -1076,6 +1118,8 @@
height: 12px;
display: inline-block;
margin-left: 6px;
+ // High margin to leave space for the increase when we go to two checks
+ margin-right: 6px;
margin-bottom: 2px;
}
@@ -1102,6 +1146,8 @@
}
}
.module-message__metadata__status-icon--delivered {
+ // We reduce the margin size to keep the overall width the same
+ margin-right: 0px;
width: 18px;
@include light-theme {
@@ -1113,6 +1159,8 @@
}
.module-message__metadata__status-icon--read,
.module-message__metadata__status-icon--viewed {
+ // We reduce the margin size to keep the overall width the same
+ margin-right: 0px;
width: 18px;
@include light-theme {
@@ -1138,6 +1186,15 @@
}
}
+.module-message__metadata__status-icon--deleted-for-everyone {
+ @include light-theme {
+ background-color: $color-gray-60;
+ }
+ @include dark-theme {
+ background-color: $color-gray-25;
+ }
+}
+
.module-message__metadata__spinner-container {
margin-left: 6px;
}
@@ -1367,6 +1424,15 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
}
}
+.module-expire-timer--deleted-for-everyone {
+ @include light-theme {
+ background-color: $color-gray-60;
+ }
+ @include dark-theme {
+ background-color: $color-gray-25;
+ }
+}
+
.module-about {
&__container {
margin-left: auto;
@@ -7845,6 +7911,8 @@ button.module-image__border-overlay:focus {
// To limit messages with things forcing them wider, like long attachment names
.module-message__container {
+ max-width: 100%;
+
&--incoming {
align-self: flex-start;
}
diff --git a/ts/components/CompositionArea.stories.tsx b/ts/components/CompositionArea.stories.tsx
index ed778ea2f..c5e40ae62 100644
--- a/ts/components/CompositionArea.stories.tsx
+++ b/ts/components/CompositionArea.stories.tsx
@@ -17,6 +17,7 @@ import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
import { fakeDraftAttachment } from '../test-both/helpers/fakeAttachment';
import { landscapeGreenUrl } from '../storybook/Fixtures';
import { RecordingState } from '../state/ducks/audioRecorder';
+import { ConversationColors } from '../types/Colors';
const i18n = setupI18n('en', enMessages);
@@ -183,3 +184,18 @@ story.add('Announcements Only group', () => (
})}
/>
));
+
+story.add('Quote', () => (
+
+));
diff --git a/ts/components/conversation/ExpireTimer.tsx b/ts/components/conversation/ExpireTimer.tsx
index 786fd2e7c..c12db463a 100644
--- a/ts/components/conversation/ExpireTimer.tsx
+++ b/ts/components/conversation/ExpireTimer.tsx
@@ -8,12 +8,13 @@ import { getIncrement, getTimerBucket } from '../../util/timer';
import { clearTimeoutIfNecessary } from '../../util/clearTimeoutIfNecessary';
export type Props = {
+ deletedForEveryone?: boolean;
+ direction?: 'incoming' | 'outgoing';
+ expirationLength: number;
+ expirationTimestamp?: number;
withImageNoCaption?: boolean;
withSticker?: boolean;
withTapToViewExpired?: boolean;
- expirationLength: number;
- expirationTimestamp: number;
- direction?: 'incoming' | 'outgoing';
};
export class ExpireTimer extends React.Component {
@@ -46,6 +47,7 @@ export class ExpireTimer extends React.Component {
public override render(): JSX.Element {
const {
+ deletedForEveryone,
direction,
expirationLength,
expirationTimestamp,
@@ -62,6 +64,9 @@ export class ExpireTimer extends React.Component {
'module-expire-timer',
`module-expire-timer--${bucket}`,
direction ? `module-expire-timer--${direction}` : null,
+ deletedForEveryone
+ ? 'module-expire-timer--deleted-for-everyone'
+ : null,
withTapToViewExpired
? `module-expire-timer--${direction}-with-tap-to-view-expired`
: null,
diff --git a/ts/components/conversation/Message.stories.tsx b/ts/components/conversation/Message.stories.tsx
index eeddd4818..7a268100d 100644
--- a/ts/components/conversation/Message.stories.tsx
+++ b/ts/components/conversation/Message.stories.tsx
@@ -381,6 +381,16 @@ story.add('Expiring', () => {
return renderBothDirections(props);
});
+story.add('Will expire but still sending', () => {
+ const props = createProps({
+ status: 'sending',
+ expirationLength: 30 * 1000,
+ text: 'For outgoing messages, we show timer immediately. Incoming, we wait until expirationStartTimestamp is present.',
+ });
+
+ return renderBothDirections(props);
+});
+
story.add('Pending', () => {
const props = createProps({
text: 'Hello there from a pal! I am sending a long message so that it will wrap a bit, since I like that look.',
@@ -607,12 +617,12 @@ story.add('Sticker', () => {
story.add('Deleted', () => {
const propsSent = createProps({
- conversationType: 'group',
+ conversationType: 'direct',
deletedForEveryone: true,
status: 'sent',
});
const propsSending = createProps({
- conversationType: 'group',
+ conversationType: 'direct',
deletedForEveryone: true,
status: 'sending',
});
@@ -645,6 +655,7 @@ story.add('Deleted with error', () => {
conversationType: 'group',
deletedForEveryone: true,
status: 'partial-sent',
+ direction: 'outgoing',
});
const propsError = createProps({
timestamp: Date.now() - 60 * 1000,
@@ -652,12 +663,13 @@ story.add('Deleted with error', () => {
conversationType: 'group',
deletedForEveryone: true,
status: 'error',
+ direction: 'outgoing',
});
return (
<>
- {renderBothDirections(propsPartialError)}
- {renderBothDirections(propsError)}
+
+
>
);
});
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 374012282..3757216c1 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -548,6 +548,7 @@ export class Message extends React.PureComponent {
private getMetadataPlacement(
{
attachments,
+ deletedForEveryone,
expirationLength,
expirationTimestamp,
shouldHideMetadata,
@@ -567,7 +568,7 @@ export class Message extends React.PureComponent {
return MetadataPlacement.NotRendered;
}
- if (!text) {
+ if (!text && !deletedForEveryone) {
return isAudio(attachments)
? MetadataPlacement.RenderedByMessageAudioComponent
: MetadataPlacement.Bottom;
@@ -598,7 +599,9 @@ export class Message extends React.PureComponent {
let result = GUESS_METADATA_WIDTH_TIMESTAMP_SIZE;
- const hasExpireTimer = Boolean(expirationLength && expirationTimestamp);
+ const hasExpireTimer = Boolean(
+ expirationLength && (expirationTimestamp || direction === 'outgoing')
+ );
if (hasExpireTimer) {
result += GUESS_METADATA_WIDTH_EXPIRE_TIMER_SIZE;
}
@@ -1464,6 +1467,9 @@ export class Message extends React.PureComponent {
`module-message__text--${direction}`,
status === 'error' && direction === 'incoming'
? 'module-message__text--error'
+ : null,
+ deletedForEveryone
+ ? 'module-message__text--delete-for-everyone'
: null
)}
dir={isRTL ? 'rtl' : undefined}
diff --git a/ts/components/conversation/MessageMetadata.tsx b/ts/components/conversation/MessageMetadata.tsx
index 9b3780652..923ca3e72 100644
--- a/ts/components/conversation/MessageMetadata.tsx
+++ b/ts/components/conversation/MessageMetadata.tsx
@@ -91,6 +91,8 @@ export const MessageMetadata = ({
className={classNames({
'module-message__metadata__date': true,
'module-message__metadata__date--with-sticker': isSticker,
+ 'module-message__metadata__date--deleted-for-everyone':
+ deletedForEveryone,
[`module-message__metadata__date--${direction}`]: !isSticker,
'module-message__metadata__date--with-image-no-caption':
withImageNoCaption,
@@ -105,6 +107,7 @@ export const MessageMetadata = ({
i18n={i18n}
timestamp={timestamp}
direction={metadataDirection}
+ deletedForEveryone={deletedForEveryone}
withImageNoCaption={withImageNoCaption}
withSticker={isSticker}
withTapToViewExpired={isTapToViewExpired}
@@ -117,14 +120,16 @@ export const MessageMetadata = ({
const className = classNames(
'module-message__metadata',
isInline && 'module-message__metadata--inline',
- withImageNoCaption && 'module-message__metadata--with-image-no-caption'
+ withImageNoCaption && 'module-message__metadata--with-image-no-caption',
+ deletedForEveryone && 'module-message__metadata--deleted-for-everyone'
);
const children = (
<>
{timestampNode}
- {expirationLength && expirationTimestamp ? (
+ {expirationLength && (expirationTimestamp || direction === 'outgoing') ? (
diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts
index 774b16ddc..d8e25122d 100644
--- a/ts/models/conversations.ts
+++ b/ts/models/conversations.ts
@@ -3669,16 +3669,22 @@ export class ConversationModel extends window.Backbone
sticker?: WhatIsThis
): Promise {
if (attachments && attachments.length) {
- const validAttachments = filter(
- attachments,
- attachment => attachment && !attachment.pending && !attachment.error
- );
- const attachmentsToUse = Array.from(take(validAttachments, 1));
+ const attachmentsToUse = Array.from(take(attachments, 1));
const isGIFQuote = isGIF(attachmentsToUse);
return Promise.all(
map(attachmentsToUse, async attachment => {
- const { fileName, thumbnail, contentType } = attachment;
+ const { path, fileName, thumbnail, contentType } = attachment;
+
+ if (!path) {
+ return {
+ contentType: isGIFQuote ? IMAGE_GIF : contentType,
+ // Our protos library complains about this field being undefined, so we
+ // force it to null
+ fileName: fileName || null,
+ thumbnail: null,
+ };
+ }
return {
contentType: isGIFQuote ? IMAGE_GIF : contentType,
@@ -3697,12 +3703,22 @@ export class ConversationModel extends window.Backbone
}
if (preview && preview.length) {
- const validPreviews = filter(preview, item => item && item.image);
- const previewsToUse = take(validPreviews, 1);
+ const previewsToUse = take(preview, 1);
return Promise.all(
map(previewsToUse, async attachment => {
const { image } = attachment;
+
+ if (!image) {
+ return {
+ contentType: IMAGE_JPEG,
+ // Our protos library complains about these fields being undefined, so we
+ // force them to null
+ fileName: null,
+ thumbnail: null,
+ };
+ }
+
const { contentType } = image;
return {
@@ -4428,7 +4444,7 @@ export class ConversationModel extends window.Backbone
return false;
}
- if (this.isGroupV1AndDisabled()) {
+ if (!isSetByOther && this.isGroupV1AndDisabled()) {
throw new Error(
'updateExpirationTimer: GroupV1 is deprecated; cannot update expiration timer'
);
diff --git a/ts/util/timer.ts b/ts/util/timer.ts
index 51167b7a5..23c4fd7bc 100644
--- a/ts/util/timer.ts
+++ b/ts/util/timer.ts
@@ -11,7 +11,14 @@ export function getIncrement(length: number): number {
return Math.ceil(length / 12);
}
-export function getTimerBucket(expiration: number, length: number): string {
+export function getTimerBucket(
+ expiration: number | undefined,
+ length: number
+): string {
+ if (!expiration) {
+ return '60';
+ }
+
const delta = expiration - Date.now();
if (delta < 0) {
return '00';