Send long text as an attachment instead of inline

Remove Android length warning

Handle incoming long message attachments

Show long download pending status in message bubble

Fix the width of the smallest spinner

Remove Android length warning from HTML templates
This commit is contained in:
Scott Nonnenberg 2019-03-13 13:38:28 -07:00
parent 0e5f405b53
commit 8c4d90df07
20 changed files with 303 additions and 140 deletions

View file

@ -822,6 +822,11 @@
"description":
"When rendering an address, used to provide context to a post office box"
},
"downloading": {
"message": "Downloading",
"description":
"Shown in the message bubble while a long message attachment is being downloaded"
},
"downloadAttachment": {
"message": "Download Attachment",
"description":
@ -1248,12 +1253,6 @@
"description":
"Warning notification that this version of the app has expired"
},
"androidMessageLengthWarning": {
"message":
"Android clients will only receive the first 2000 characters of this message.",
"description":
"Warning that long messages could not get received completely by Android clients."
},
"upgrade": {
"message": "Upgrade",
"description":

View file

@ -122,9 +122,6 @@
<div class='capture-audio'>
<button class='microphone'></button>
</div>
<div class='android-length-warning'>
{{ android-length-warning }}
</div>
<div class='choose-file'>
<button class='paperclip thumbnail'></button>
<input type='file' class='file-input' multiple='multiple'>

View file

@ -921,12 +921,21 @@
messageWithSchema.attachments.map(loadAttachmentData)
);
const {
body: messageBody,
attachments: finalAttachments,
} = Whisper.Message.getLongMessageAttachment({
body,
attachments: attachmentsWithData,
now,
});
// Special-case the self-send case - we send only a sync message
if (this.isMe()) {
const dataMessage = await textsecure.messaging.getMessageProto(
destination,
body,
attachmentsWithData,
messageBody,
finalAttachments,
quote,
preview,
now,
@ -945,8 +954,8 @@
case Message.PRIVATE:
return textsecure.messaging.sendMessageToNumber(
destination,
body,
attachmentsWithData,
messageBody,
finalAttachments,
quote,
preview,
now,
@ -958,8 +967,8 @@
return textsecure.messaging.sendMessageToGroup(
destination,
groupNumbers,
body,
attachmentsWithData,
messageBody,
finalAttachments,
quote,
preview,
now,

View file

@ -26,6 +26,7 @@
loadPreviewData,
upgradeMessageSchema,
} = window.Signal.Migrations;
const { bytesFromString } = window.Signal.Crypto;
window.AccountCache = Object.create(null);
window.AccountJobs = Object.create(null);
@ -480,6 +481,7 @@
return {
text: this.createNonBreakingLastSeparator(this.get('body')),
textPending: this.get('bodyPending'),
id: this.id,
direction: this.isIncoming() ? 'incoming' : 'outgoing',
timestamp: this.get('sent_at'),
@ -821,6 +823,12 @@
const attachmentsWithData = await Promise.all(
(this.get('attachments') || []).map(loadAttachmentData)
);
const { body, attachments } = Whisper.Message.getLongMessageAttachment({
body: this.get('body'),
attachments: attachmentsWithData,
now: this.get('sent_at'),
});
const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview'));
@ -829,8 +837,8 @@
const [number] = numbers;
const dataMessage = await textsecure.messaging.getMessageProto(
number,
this.get('body'),
attachmentsWithData,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
@ -847,8 +855,8 @@
const [number] = numbers;
promise = textsecure.messaging.sendMessageToNumber(
number,
this.get('body'),
attachmentsWithData,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
@ -863,9 +871,9 @@
promise = textsecure.messaging.sendMessage(
{
recipients: numbers,
body: this.get('body'),
body,
timestamp: this.get('sent_at'),
attachments: attachmentsWithData,
attachments,
quote: quoteWithData,
preview: previewWithData,
needsSync: !this.get('synced'),
@ -905,6 +913,12 @@
const attachmentsWithData = await Promise.all(
(this.get('attachments') || []).map(loadAttachmentData)
);
const { body, attachments } = Whisper.Message.getLongMessageAttachment({
body: this.get('body'),
attachments: attachmentsWithData,
now: this.get('sent_at'),
});
const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview'));
@ -912,8 +926,8 @@
if (number === this.OUR_NUMBER) {
const dataMessage = await textsecure.messaging.getMessageProto(
number,
this.get('body'),
attachmentsWithData,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
@ -928,8 +942,8 @@
);
const promise = textsecure.messaging.sendMessageToNumber(
number,
this.get('body'),
attachmentsWithData,
body,
attachments,
quoteWithData,
previewWithData,
this.get('sent_at'),
@ -1255,9 +1269,34 @@
async queueAttachmentDownloads() {
const messageId = this.id;
let count = 0;
let bodyPending;
const [longMessageAttachments, normalAttachments] = _.partition(
this.get('attachments') || [],
attachment =>
attachment.contentType === Whisper.Message.LONG_MESSAGE_CONTENT_TYPE
);
if (longMessageAttachments.length > 1) {
window.log.error(
`Received more than one long message attachment in message ${this.idForLogging()}`
);
}
if (longMessageAttachments.length > 0) {
count += 1;
bodyPending = true;
await window.Signal.AttachmentDownloads.addJob(
longMessageAttachments[0],
{
messageId,
type: 'long-message',
index: 0,
}
);
}
const attachments = await Promise.all(
(this.get('attachments') || []).map((attachment, index) => {
normalAttachments.map((attachment, index) => {
count += 1;
return window.Signal.AttachmentDownloads.addJob(attachment, {
messageId,
@ -1351,7 +1390,7 @@
}
if (count > 0) {
this.set({ attachments, preview, contact, quote, group });
this.set({ bodyPending, attachments, preview, contact, quote, group });
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
@ -1837,6 +1876,39 @@
},
});
// Receive will be enabled before we enable send
Whisper.Message.LONG_MESSAGE_SEND_DISABLED = true;
Whisper.Message.LONG_MESSAGE_CONTENT_TYPE = 'text/x-signal-plain';
Whisper.Message.getLongMessageAttachment = ({ body, attachments, now }) => {
if (Whisper.Message.LONG_MESSAGE_SEND_DISABLED) {
return {
body,
attachments,
};
}
if (body.length <= 2048) {
return {
body,
attachments,
};
}
const data = bytesFromString(body);
const attachment = {
contentType: Whisper.Message.LONG_MESSAGE_CONTENT_TYPE,
fileName: `long-message-${now}.txt`,
data,
size: data.byteLength,
};
return {
body: body.slice(0, 2048),
attachments: [attachment, ...attachments],
};
};
Whisper.Message.refreshExpirationTimer = () =>
Whisper.ExpiringMessagesListener.update();

View file

@ -11,6 +11,7 @@ const {
saveMessage,
setAttachmentDownloadJobPending,
} = require('./data');
const { stringFromBytes } = require('./crypto');
module.exports = {
start,
@ -312,6 +313,19 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) {
const logPrefix = `${message.idForLogging()} (type: ${type}, index: ${index})`;
if (type === 'long-message') {
try {
const { data } = await Signal.Migrations.loadAttachmentData(attachment);
message.set({
body: attachment.isError ? message.get('body') : stringFromBytes(data),
bodyPending: false,
});
} finally {
Signal.Migrations.deleteAttachmentData(attachment.path);
}
return;
}
if (type === 'attachment') {
const attachments = message.get('attachments');
if (!attachments || attachments.length <= index) {

View file

@ -80,7 +80,6 @@
render_attributes() {
return {
'send-message': i18n('sendMessage'),
'android-length-warning': i18n('androidMessageLengthWarning'),
};
},
initialize(options) {
@ -542,13 +541,6 @@
this.$('.capture-audio').show();
}
},
toggleLengthWarning() {
if (this.$('.send-message').val().length > 2000) {
this.$('.android-length-warning').show();
} else {
this.$('.android-length-warning').hide();
}
},
captureAudio(e) {
e.preventDefault();
@ -2047,7 +2039,6 @@
return;
}
this.toggleMicrophone();
this.toggleLengthWarning();
this.view.measureScrollPosition();
window.autosize(this.$messageField);

View file

@ -138,12 +138,6 @@
.module-spinner__arc--incoming {
background-color: $color-gray-60;
}
.module-spinner__circle--small-incoming {
background-color: $color-white-04;
}
.module-spinner__arc--small-incoming {
background-color: $color-gray-60;
}
.module-spinner__circle--outgoing {
background-color: $color-white-04;
@ -151,12 +145,6 @@
.module-spinner__arc--outgoing {
background-color: $color-white;
}
.module-spinner__circle--small-outgoing {
background-color: $color-white-04;
}
.module-spinner__arc--small-outgoing {
background-color: $color-white;
}
&.dark-theme {
// _modules

View file

@ -2787,34 +2787,49 @@
}
}
// In these --small and --mini sizes, we're exploding our @color-svg mixin so we don't
// have to duplicate our background colors for the dark/ios/size matrix.
.module-spinner__container--small {
height: 24px;
width: 24px;
}
.module-spinner__circle--small {
@include color-svg('../images/spinner-track-24.svg', $color-white-04);
-webkit-mask: url('../images/spinner-track-24.svg') no-repeat center;
-webkit-mask-size: 100%;
height: 24px;
width: 24px;
}
.module-spinner__arc--small {
@include color-svg('../images/spinner-24.svg', $color-gray-60);
-webkit-mask: url('../images/spinner-24.svg') no-repeat center;
-webkit-mask-size: 100%;
height: 24px;
width: 24px;
}
.module-spinner__container--mini {
height: 14px;
width: 14px;
}
.module-spinner__circle--mini {
-webkit-mask: url('../images/spinner-track-24.svg') no-repeat center;
-webkit-mask-size: 100%;
height: 14px;
width: 14px;
}
.module-spinner__arc--mini {
-webkit-mask: url('../images/spinner-24.svg') no-repeat center;
-webkit-mask-size: 100%;
height: 14px;
width: 14px;
}
.module-spinner__circle--incoming {
background-color: $color-white-04;
}
.module-spinner__arc--incoming {
background-color: $color-white;
}
.module-spinner__circle--small-incoming {
background-color: $color-white-04;
}
.module-spinner__arc--small-incoming {
background-color: $color-white;
}
// Module: Highlighted Message Body

View file

@ -1442,12 +1442,6 @@ body.dark-theme {
.module-spinner__arc {
background-color: $color-gray-05;
}
.module-spinner__circle--small {
background-color: $color-white-04;
}
.module-spinner__arc--small {
background-color: $color-gray-05;
}
.module-spinner__circle--incoming {
background-color: $color-white-04;
@ -1455,12 +1449,6 @@ body.dark-theme {
.module-spinner__arc--incoming {
background-color: $color-gray-05;
}
.module-spinner__circle--small-incoming {
background-color: $color-white-04;
}
.module-spinner__arc--small-incoming {
background-color: $color-gray-05;
}
.module-spinner__circle--outgoing {
background-color: $color-white-04;
@ -1468,12 +1456,6 @@ body.dark-theme {
.module-spinner__arc--outgoing {
background-color: $color-gray-05;
}
.module-spinner__circle--small-outgoing {
background-color: $color-white-04;
}
.module-spinner__arc--small-outgoing {
background-color: $color-gray-05;
}
// Module: Caption Editor

View file

@ -112,9 +112,6 @@
<div class='capture-audio'>
<button class='microphone'></button>
</div>
<div class='android-length-warning'>
{{ android-length-warning }}
</div>
<div class='choose-file'>
<button class='paperclip thumbnail'></button>
<input type='file' class='file-input' multiple='multiple'>

View file

@ -2,7 +2,10 @@
```jsx
<util.ConversationContext theme={util.theme}>
<Spinner />
<Spinner size="normal" />
<div style={{ backgroundColor: '#2090ea' }}>
<Spinner size="normal" />
</div>
</util.ConversationContext>
```
@ -10,6 +13,20 @@
```jsx
<util.ConversationContext theme={util.theme}>
<Spinner small />
<Spinner size="small" />
<div style={{ backgroundColor: '#2090ea' }}>
<Spinner size="small" />
</div>
</util.ConversationContext>
```
#### Mini
```jsx
<util.ConversationContext theme={util.theme}>
<Spinner size="mini" />
<div style={{ backgroundColor: '#2090ea' }}>
<Spinner size="mini" />
</div>
</util.ConversationContext>
```

View file

@ -2,43 +2,37 @@ import React from 'react';
import classNames from 'classnames';
interface Props {
small?: boolean;
size: 'small' | 'mini' | 'normal';
direction?: string;
}
export class Spinner extends React.Component<Props> {
public render() {
const { small, direction } = this.props;
const { size, direction } = this.props;
return (
<div
className={classNames(
'module-spinner__container',
`module-spinner__container--${size}`,
direction ? `module-spinner__container--${direction}` : null,
small ? 'module-spinner__container--small' : null,
small && direction
? `module-spinner__container--small-${direction}`
: null
direction ? `module-spinner__container--${size}-${direction}` : null
)}
>
<div
className={classNames(
'module-spinner__circle',
`module-spinner__circle--${size}`,
direction ? `module-spinner__circle--${direction}` : null,
small ? 'module-spinner__circle--small' : null,
small && direction
? `module-spinner__circle--small-${direction}`
: null
direction ? `module-spinner__circle--${size}-${direction}` : null
)}
/>
<div
className={classNames(
'module-spinner__arc',
`module-spinner__arc--${size}`,
direction ? `module-spinner__arc--${direction}` : null,
small ? 'module-spinner__arc--small' : null,
small && direction
? `module-spinner__arc--small-${direction}`
: null
direction ? `module-spinner__arc--${size}-${direction}` : null
)}
/>
</div>

View file

@ -94,7 +94,7 @@ export class Image extends React.Component<Props> {
}}
// alt={i18n('loading')}
>
<Spinner />
<Spinner size="normal" />
</div>
) : (
<img

View file

@ -474,13 +474,17 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
<Message
authorColor="purple"
direction="incoming"
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget condimentum tellus. Aenean vulputate, dui a gravida rhoncus, mi orci varius urna, ut placerat felis ex ac elit. In pulvinar quis velit convallis varius. Quisque mattis, metus id lobortis porttitor, lacus ex laoreet dui, sit amet laoreet massa leo sed tellus. Phasellus iaculis pulvinar bibendum. In vitae imperdiet felis. Vivamus lacinia eros nec arcu varius, sodales faucibus nulla molestie. Etiam luctus lectus sit amet nulla facilisis, a porta mi tempus. Donec sit amet convallis ipsum.
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam efficitur finibus tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu metus leo. Nullam consequat leo ut accumsan aliquam. In est elit, faucibus vel arcu vitae, dapibus egestas nunc. Curabitur nec orci semper, auctor justo ornare, sagittis massa. Aliquam ultrices sem ac ex vestibulum dapibus. Etiam erat purus, interdum sit amet magna vitae, elementum lacinia leo. Duis vel mauris dui. Morbi sed accumsan erat, at facilisis metus. Nullam molestie lectus eleifend congue ultrices. Nunc porta at justo semper egestas. Proin non iaculis nibh. Cras sit amet urna dignissim, venenatis arcu a, pulvinar ipsum.
In eros risus, posuere non viverra at, finibus ac elit. Nunc convallis vulputate risus. Donec ligula justo, lacinia id vulputate in, semper non nibh. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque porttitor neque a metus dapibus varius. Sed luctus purus vel semper rhoncus. In imperdiet risus ut convallis porttitor. Fusce vel ligula placerat, imperdiet ante vel, mollis ipsum.
Integer et justo ut urna tempor ultrices. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In bibendum a nulla non blandit. In iaculis id orci maximus elementum. Mauris ultricies ipsum et magna iaculis, non porta orci elementum. Curabitur ipsum magna, porttitor id cursus nec, euismod at orci. Sed et ex id neque hendrerit auctor sed et mauris. In hac habitasse platea dictumst.
Etiam ultricies tortor eget mi sollicitudin suscipit. Nullam non ligula lacinia, ornare tortor in, tempor enim. Nullam nec ullamcorper enim. Vestibulum aliquet leo eget nisl aliquet vulputate. Duis quis nisl ligula. Nunc pulvinar lacus urna. Morbi imperdiet tortor eu finibus dictum. Cras ullamcorper aliquet eros, non malesuada tellus cursus eget.
Aliquam erat volutpat. Mauris quis erat luctus enim tincidunt fringilla. Vestibulum ornare, erat sit amet pretium gravida, tortor ipsum pretium eros, ac congue mauris elit vel elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas ultrices neque vulputate, pellentesque massa non, imperdiet justo. Curabitur vel ex non enim volutpat fringilla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In gravida consectetur justo sit amet feugiat. Vivamus non eros dignissim, interdum magna at, suscipit mauris. Duis sit amet dui tempor, ornare arcu ultrices, convallis neque. Proin quis risus leo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nunc lectus sapien, feugiat sit amet orci nec, consectetur vehicula odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas porta scelerisque egestas.
Cras sagittis, sapien vel gravida pellentesque, sem sem semper velit, vel congue ligula leo aliquet massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur eros diam, tempor sed lacus non, commodo imperdiet quam. Praesent eget tristique lectus, sit amet iaculis felis. Morbi molestie dui blandit augue vulputate tempus. Nulla facilisi. Nulla dictum felis eu nulla rhoncus, sed ultricies est scelerisque. Nam risus arcu, sodales at nisl eget, volutpat elementum lacus. Morbi dictum condimentum lorem, at placerat nulla eleifend a. Vestibulum hendrerit diam vulputate, sollicitudin urna vel, luctus nisl. Mauris semper sem quam, sed venenatis quam convallis in. Donec hendrerit, nibh ut mattis congue, quam nibh consectetur magna, eu posuere urna orci et turpis. Integer vitae arcu vitae est varius maximus. Sed ultrices tortor lacus, venenatis pulvinar nibh ullamcorper sit amet. Nulla vehicula metus sed diam gravida auctor sed cursus enim. Curabitur viverra non erat et mollis.`}
Fusce diam massa, lacinia sit amet vehicula vitae, pretium sed augue. Duis diam velit, efficitur eget fringilla vel, pharetra eu lacus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas et convallis tellus. Aenean in orci tincidunt, finibus nulla ut, aliquam quam. Nullam feugiat egestas urna, ultricies suscipit justo venenatis eget. Curabitur sollicitudin odio eu tincidunt porta. Nullam in metus in purus rutrum varius et sit amet nibh. Nunc at efficitur turpis, a tincidunt dolor.
Nam non leo euismod, volutpat leo quis, semper orci. Proin malesuada ultrices ex, nec fringilla ante condimentum eu. Sed vel gravida nibh. Vivamus sed tincidunt sem. Phasellus arcu orci, condimentum nec fringilla ac, maximus a arcu. Mauris sit amet sodales nisl. Etiam molestie consequat auctor. Proin auctor pulvinar mi vitae consequat.
Phasellus commodo viverra condimentum. Nam vitae facilisis nibh, dapibus eleifend nisl. Quisque eu massa nunc.`}
timestamp={Date.now()}
i18n={util.i18n}
/>
@ -490,13 +494,53 @@ Note that timestamp and status can be hidden with the `collapseMetadata` boolean
authorColor="purple"
direction="outgoing"
status="delivered"
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eget condimentum tellus. Aenean vulputate, dui a gravida rhoncus, mi orci varius urna, ut placerat felis ex ac elit. In pulvinar quis velit convallis varius. Quisque mattis, metus id lobortis porttitor, lacus ex laoreet dui, sit amet laoreet massa leo sed tellus. Phasellus iaculis pulvinar bibendum. In vitae imperdiet felis. Vivamus lacinia eros nec arcu varius, sodales faucibus nulla molestie. Etiam luctus lectus sit amet nulla facilisis, a porta mi tempus. Donec sit amet convallis ipsum.
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam efficitur finibus tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu metus leo. Nullam consequat leo ut accumsan aliquam. In est elit, faucibus vel arcu vitae, dapibus egestas nunc. Curabitur nec orci semper, auctor justo ornare, sagittis massa. Aliquam ultrices sem ac ex vestibulum dapibus. Etiam erat purus, interdum sit amet magna vitae, elementum lacinia leo. Duis vel mauris dui. Morbi sed accumsan erat, at facilisis metus. Nullam molestie lectus eleifend congue ultrices. Nunc porta at justo semper egestas. Proin non iaculis nibh. Cras sit amet urna dignissim, venenatis arcu a, pulvinar ipsum.
In eros risus, posuere non viverra at, finibus ac elit. Nunc convallis vulputate risus. Donec ligula justo, lacinia id vulputate in, semper non nibh. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque porttitor neque a metus dapibus varius. Sed luctus purus vel semper rhoncus. In imperdiet risus ut convallis porttitor. Fusce vel ligula placerat, imperdiet ante vel, mollis ipsum.
Integer et justo ut urna tempor ultrices. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In bibendum a nulla non blandit. In iaculis id orci maximus elementum. Mauris ultricies ipsum et magna iaculis, non porta orci elementum. Curabitur ipsum magna, porttitor id cursus nec, euismod at orci. Sed et ex id neque hendrerit auctor sed et mauris. In hac habitasse platea dictumst.
Etiam ultricies tortor eget mi sollicitudin suscipit. Nullam non ligula lacinia, ornare tortor in, tempor enim. Nullam nec ullamcorper enim. Vestibulum aliquet leo eget nisl aliquet vulputate. Duis quis nisl ligula. Nunc pulvinar lacus urna. Morbi imperdiet tortor eu finibus dictum. Cras ullamcorper aliquet eros, non malesuada tellus cursus eget.
Aliquam erat volutpat. Mauris quis erat luctus enim tincidunt fringilla. Vestibulum ornare, erat sit amet pretium gravida, tortor ipsum pretium eros, ac congue mauris elit vel elit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Maecenas ultrices neque vulputate, pellentesque massa non, imperdiet justo. Curabitur vel ex non enim volutpat fringilla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In gravida consectetur justo sit amet feugiat. Vivamus non eros dignissim, interdum magna at, suscipit mauris. Duis sit amet dui tempor, ornare arcu ultrices, convallis neque. Proin quis risus leo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nunc lectus sapien, feugiat sit amet orci nec, consectetur vehicula odio. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas porta scelerisque egestas.
Cras sagittis, sapien vel gravida pellentesque, sem sem semper velit, vel congue ligula leo aliquet massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Curabitur eros diam, tempor sed lacus non, commodo imperdiet quam. Praesent eget tristique lectus, sit amet iaculis felis. Morbi molestie dui blandit augue vulputate tempus. Nulla facilisi. Nulla dictum felis eu nulla rhoncus, sed ultricies est scelerisque. Nam risus arcu, sodales at nisl eget, volutpat elementum lacus. Morbi dictum condimentum lorem, at placerat nulla eleifend a. Vestibulum hendrerit diam vulputate, sollicitudin urna vel, luctus nisl. Mauris semper sem quam, sed venenatis quam convallis in. Donec hendrerit, nibh ut mattis congue, quam nibh consectetur magna, eu posuere urna orci et turpis. Integer vitae arcu vitae est varius maximus. Sed ultrices tortor lacus, venenatis pulvinar nibh ullamcorper sit amet. Nulla vehicula metus sed diam gravida auctor sed cursus enim. Curabitur viverra non erat et mollis.`}
Fusce diam massa, lacinia sit amet vehicula vitae, pretium sed augue. Duis diam velit, efficitur eget fringilla vel, pharetra eu lacus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas et convallis tellus. Aenean in orci tincidunt, finibus nulla ut, aliquam quam. Nullam feugiat egestas urna, ultricies suscipit justo venenatis eget. Curabitur sollicitudin odio eu tincidunt porta. Nullam in metus in purus rutrum varius et sit amet nibh. Nunc at efficitur turpis, a tincidunt dolor.
Nam non leo euismod, volutpat leo quis, semper orci. Proin malesuada ultrices ex, nec fringilla ante condimentum eu. Sed vel gravida nibh. Vivamus sed tincidunt sem. Phasellus arcu orci, condimentum nec fringilla ac, maximus a arcu. Mauris sit amet sodales nisl. Etiam molestie consequat auctor. Proin auctor pulvinar mi vitae consequat.
Phasellus commodo viverra condimentum. Nam vitae facilisis nibh, dapibus eleifend nisl. Quisque eu massa nunc.`}
timestamp={Date.now()}
i18n={util.i18n}
/>
</li>
</util.ConversationContext>
```
### Pending long message download
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<li>
<Message
authorColor="purple"
direction="incoming"
textPending={true}
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis fringilla nulla velit, id finibus orci porttitor at. Donec eget orci nunc. Fusce nisl arcu, porttitor eget eleifend id, malesuada et diam. Donec porta id magna vel egestas. Donec justo odio, dignissim ac lorem in, bibendum congue arcu. Sed aliquam, tortor non ultricies pretium, orci dui auctor augue, id efficitur orci erat a velit. Morbi efficitur ante quis ex malesuada, vitae eleifend risus dapibus. Donec sollicitudin justo sed viverra vulputate. Donec iaculis dolor velit, sit amet feugiat lacus gravida in. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In commodo, lacus lacinia efficitur rutrum, purus neque aliquet turpis, ac tincidunt dolor quam vitae dolor. Vestibulum ultrices orci non finibus lobortis. Etiam in efficitur augue, at pulvinar diam. Praesent gravida erat vitae dolor varius, eu fermentum justo fermentum. Nullam feugiat orci ipsum, ut congue orci varius in. Duis arcu elit, mattis ac nisi at, hendrerit pretium magna. Quisque volutpat ipsum leo, at ultrices arcu rhoncus mattis. Quisque pellentesque nisl suscipit tempor aliquet. Quisque venenatis massa eget ex fermentum, et iaculis dui porttitor. Nam sed tortor tincidunt, eleifend diam vitae, facilisis erat. Suspendisse ornare justo molestie felis bibendum, non laoreet urna posuere. Ut in felis vel mauris commodo semper et non massa. Vivamus vitae sagittis est. Nullam faucibus justo metus, eget aliquet mi vestibulum sit amet.
Nulla tincidunt dui non massa aliquam, nec luctus turpis dapibus. Duis sollicitudin consectetur justo ut volutpat. Suspendisse a consectetur ligula, nec rutrum felis. Curabitur neque lorem, finibus id molestie at, ultricies vel tortor. Praesent porttitor augue non magna blandit, quis pulvinar risus iaculis. Sed at lorem risus. Pellentesque laoreet odio et justo blandit dignissim. Curabitur eget venenatis leo, eget vehicula sem. Proin eros nisi, faucibus et malesuada a, porta id tortor. Etiam imperdiet eleifend commodo. Nunc at malesuada mi, vitae volutpat sema`}
timestamp={Date.now()}
i18n={util.i18n}
/>
</li>
<li>
<Message
authorColor="purple"
direction="outgoing"
status="delivered"
textPending={true}
text={`Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis fringilla nulla velit, id finibus orci porttitor at. Donec eget orci nunc. Fusce nisl arcu, porttitor eget eleifend id, malesuada et diam. Donec porta id magna vel egestas. Donec justo odio, dignissim ac lorem in, bibendum congue arcu. Sed aliquam, tortor non ultricies pretium, orci dui auctor augue, id efficitur orci erat a velit. Morbi efficitur ante quis ex malesuada, vitae eleifend risus dapibus. Donec sollicitudin justo sed viverra vulputate. Donec iaculis dolor velit, sit amet feugiat lacus gravida in. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In commodo, lacus lacinia efficitur rutrum, purus neque aliquet turpis, ac tincidunt dolor quam vitae dolor. Vestibulum ultrices orci non finibus lobortis. Etiam in efficitur augue, at pulvinar diam. Praesent gravida erat vitae dolor varius, eu fermentum justo fermentum. Nullam feugiat orci ipsum, ut congue orci varius in. Duis arcu elit, mattis ac nisi at, hendrerit pretium magna. Quisque volutpat ipsum leo, at ultrices arcu rhoncus mattis. Quisque pellentesque nisl suscipit tempor aliquet. Quisque venenatis massa eget ex fermentum, et iaculis dui porttitor. Nam sed tortor tincidunt, eleifend diam vitae, facilisis erat. Suspendisse ornare justo molestie felis bibendum, non laoreet urna posuere. Ut in felis vel mauris commodo semper et non massa. Vivamus vitae sagittis est. Nullam faucibus justo metus, eget aliquet mi vestibulum sit amet.
Nulla tincidunt dui non massa aliquam, nec luctus turpis dapibus. Duis sollicitudin consectetur justo ut volutpat. Suspendisse a consectetur ligula, nec rutrum felis. Curabitur neque lorem, finibus id molestie at, ultricies vel tortor. Praesent porttitor augue non magna blandit, quis pulvinar risus iaculis. Sed at lorem risus. Pellentesque laoreet odio et justo blandit dignissim. Curabitur eget venenatis leo, eget vehicula sem. Proin eros nisi, faucibus et malesuada a, porta id tortor. Etiam imperdiet eleifend commodo. Nunc at malesuada mi, vitae volutpat sema`}
timestamp={Date.now()}
i18n={util.i18n}
/>

View file

@ -49,6 +49,7 @@ interface LinkPreviewType {
export interface Props {
disableMenu?: boolean;
text?: string;
textPending?: boolean;
id?: string;
collapseMetadata?: boolean;
direction: 'incoming' | 'outgoing';
@ -196,6 +197,7 @@ export class Message extends React.PureComponent<Props, State> {
i18n,
status,
text,
textPending,
timestamp,
} = this.props;
@ -247,7 +249,12 @@ export class Message extends React.PureComponent<Props, State> {
/>
) : null}
<span className="module-message__metadata__spacer" />
{direction === 'outgoing' && status !== 'error' ? (
{textPending ? (
<div className="module-message__metadata__spinner-container">
<Spinner size="mini" direction={direction} />
</div>
) : null}
{!textPending && direction === 'outgoing' && status !== 'error' ? (
<div
className={classNames(
'module-message__metadata__status-icon',
@ -383,7 +390,7 @@ export class Message extends React.PureComponent<Props, State> {
>
{pending ? (
<div className="module-message__generic-attachment__spinner-container">
<Spinner small={true} direction={direction} />
<Spinner size="small" direction={direction} />
</div>
) : (
<div className="module-message__generic-attachment__icon-container">
@ -647,7 +654,7 @@ export class Message extends React.PureComponent<Props, State> {
}
public renderText() {
const { text, i18n, direction, status } = this.props;
const { text, textPending, i18n, direction, status } = this.props;
const contents =
direction === 'incoming' && status === 'error'
@ -669,7 +676,11 @@ export class Message extends React.PureComponent<Props, State> {
: null
)}
>
<MessageBody text={contents || ''} i18n={i18n} />
<MessageBody
text={contents || ''}
i18n={i18n}
textPending={textPending}
/>
</div>
);
}

View file

@ -36,13 +36,13 @@
### Jumbomoji disabled
```jsx
<MessageBody text="🔥" disableJumbomoji i18n={util.i18n} />
<MessageBody text="🔥" disableJumbomoji={true} i18n={util.i18n} />
```
### Links disabled
```jsx
<MessageBody text="http://somewhere.com" disableLinks i18n={util.i18n} />
<MessageBody text="http://somewhere.com" disableLinks={true} i18n={util.i18n} />
```
### Emoji in link
@ -50,3 +50,24 @@
```jsx
<MessageBody text="http://somewhere.com?s=🔥\nCool, huh?" i18n={util.i18n} />
```
### Text pending
```jsx
<MessageBody
text="http://somewhere.com?s=🔥\nCool, huh?"
textPending={true}
i18n={util.i18n}
/>
```
### Text pending, disable links
```jsx
<MessageBody
text="http://somewhere.com?s=🔥\nCool, huh?"
textPending={true}
disableLinks={true}
i18n={util.i18n}
/>
```

View file

@ -9,6 +9,7 @@ import { LocalizerType, RenderTextCallbackType } from '../../types/Util';
interface Props {
text: string;
textPending?: boolean;
/** If set, all emoji will be the same size. Otherwise, just one emoji will be large. */
disableJumbomoji?: boolean;
/** If set, links will be left alone instead of turned into clickable `<a>` tags. */
@ -50,23 +51,48 @@ const renderEmoji = ({
* them for you.
*/
export class MessageBody extends React.Component<Props> {
public render() {
const { text, disableJumbomoji, disableLinks, i18n } = this.props;
const sizeClass = disableJumbomoji ? undefined : getSizeClass(text);
if (disableLinks) {
return renderEmoji({
i18n,
text,
sizeClass,
key: 0,
renderNonEmoji: renderNewLines,
});
}
public addDownloading(jsx: JSX.Element): JSX.Element {
const { i18n, textPending } = this.props;
return (
<span>
{jsx}
{textPending ? (
<span className="module-message-body__highlight">
{' '}
{i18n('downloading')}
</span>
) : null}
</span>
);
}
public render() {
const {
text,
textPending,
disableJumbomoji,
disableLinks,
i18n,
} = this.props;
const sizeClass = disableJumbomoji ? undefined : getSizeClass(text);
const textWithPending = textPending ? `${text}...` : text;
if (disableLinks) {
return this.addDownloading(
renderEmoji({
i18n,
text: textWithPending,
sizeClass,
key: 0,
renderNonEmoji: renderNewLines,
})
);
}
return this.addDownloading(
<Linkify
text={text}
text={textWithPending}
renderNonLink={({ key, text: nonLinkText }) => {
return renderEmoji({
i18n,

View file

@ -25,11 +25,12 @@ export function renderAvatar({
const avatarPath = avatar && avatar.avatar && avatar.avatar.path;
const pending = avatar && avatar.avatar && avatar.avatar.pending;
const name = getName(contact) || '';
const spinnerSize = size < 50 ? 'small' : 'normal';
if (pending) {
return (
<div className="module-embedded-contact__spinner-container">
<Spinner small={size < 50} direction={direction} />
<Spinner size={spinnerSize} direction={direction} />
</div>
);
}

View file

@ -203,22 +203,6 @@
"updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes"
},
{
"rule": "jQuery-wrap(",
"path": "js/models/messages.js",
"line": " return this.send(wrap(promise));",
"lineNumber": 938,
"reasonCategory": "falseMatch",
"updated": "2018-10-05T23:12:28.961Z"
},
{
"rule": "jQuery-wrap(",
"path": "js/models/messages.js",
"line": " return wrap(",
"lineNumber": 1185,
"reasonCategory": "falseMatch",
"updated": "2018-10-05T23:12:28.961Z"
},
{
"rule": "jQuery-wrap(",
"path": "js/modules/crypto.js",

View file

@ -50,6 +50,7 @@ const results: Array<ExceptionType> = [];
const excludedFiles = [
// High-traffic files in our project
'^js/models/messages.js',
'^js/views/conversation_view.js',
'^js/views/file_input_view.js',
'^js/background.js',