Show current quoted message above composition field

Note that substantial changes will be required for the updated Android
mockups, putting the quotation into the text box next to the attachment
preview.
This commit is contained in:
Scott Nonnenberg 2018-04-18 13:06:33 -07:00
parent e66f9faf33
commit c71dcf0139
No known key found for this signature in database
GPG key ID: 5F82280C35134661
6 changed files with 314 additions and 2 deletions

1
images/close-circle.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z" /></svg>

After

Width:  |  Height:  |  Size: 495 B

View file

@ -130,7 +130,7 @@
'scroll-to-message',
this.scrollToMessage
);
this.listenTo(this.model.messageCollection, 'reply', this.setReplyMessage);
this.listenTo(this.model.messageCollection, 'reply', this.setQuoteMessage);
this.lazyUpdateVerified = _.debounce(
this.model.updateVerified.bind(this.model),
@ -275,6 +275,9 @@
if (this.scrollDownButton) {
this.scrollDownButton.remove();
}
if (this.quoteView) {
this.quoteView.remove();
}
if (this.panels && this.panels.length) {
for (let i = 0, max = this.panels.length; i < max; i += 1) {
const panel = this.panels[i];
@ -1062,9 +1065,66 @@
this.focusMessageField();
},
setMessageReply(message) {
setQuoteMessage(message) {
this.quotedMessage = message;
console.log('setMessageReply', this.quotedMessage);
this.renderQuotedMessage();
},
makeQuote(quotedMessage) {
const contact = quotedMessage.getContact();
const attachments = quotedMessage.get('attachments');
const first = attachments ? attachments[0] : null;
return {
author: contact.id,
id: quotedMessage.get('sent_at'),
text: quotedMessage.get('body'),
attachments: !first ? [] : [{
contentType: first.contentType,
fileName: first.fileName,
}],
};
},
renderQuotedMessage() {
if (this.quoteView) {
this.quoteView.remove();
this.quoteView = null;
}
if (!this.quotedMessage) {
this.updateMessageFieldSize({});
return;
}
const message = new Whisper.Message({
quote: this.makeQuote(this.quotedMessage),
});
console.log('quoted message attributes', message.attributes);
message.quotedMessage = this.quotedMessage;
const props = Object.assign({}, message.getPropsForQuote(), {
onClose: () => {
console.log('onClose!');
this.setQuoteMessage(null);
},
});
this.listenTo(message, 'scroll-to-message', this.scrollToMessage);
console.log('props', props);
const contact = this.quotedMessage.getContact();
if (contact) {
this.listenTo(contact, 'change:color', this.renderQuotedMesage);
}
this.quoteView = new Whisper.ReactWrapperView({
className: 'quote-wrapper',
Component: window.Signal.Components.Quote,
props,
});
this.$('.bottom-bar').prepend(this.quoteView.el);
this.updateMessageFieldSize({});
},
async sendMessage(e) {
@ -1168,9 +1228,15 @@
const $attachmentPreviews = this.$('.attachment-previews');
const $bottomBar = this.$('.bottom-bar');
const includeMargin = true;
const quoteHeight = this.quoteView
? this.quoteView.$el.outerHeight(includeMargin)
: 0;
const height = this.$messageField.outerHeight() +
$attachmentPreviews.outerHeight() +
this.$emojiPanelContainer.outerHeight() +
quoteHeight +
parseInt($bottomBar.css('min-height'), 10);
$bottomBar.outerHeight(height);

View file

@ -701,6 +701,7 @@ span.status {
cursor: auto;
}
position: relative;
cursor: pointer;
display: flex;
flex-direction: row;
@ -718,6 +719,9 @@ span.status {
// Accent color border:
border-left-width: 3px;
border-left-style: solid;
border-top: 1px solid lightgray;
border-bottom: 1px solid lightgray;
border-right: 1px solid lightgray;
.primary {
flex-grow: 1;
@ -766,6 +770,16 @@ span.status {
}
}
.close-container {
position: absolute;
top: 0px;
right: 0px;
height: 18px;
width: 18px;
@include color-svg('../images/x.svg', white);
}
.icon-container {
flex: initial;
min-width: 48px;
@ -833,6 +847,15 @@ span.status {
margin-top: $android-bubble-quote-padding - $android-bubble-padding-vertical;
}
.bottom-bar .quoted-message {
margin: 0px;
}
.bottom-bar .quote-wrapper {
margin-right: 5px;
margin-bottom: 5px;
}
.incoming .quoted-message {
background-color: rgba(white, 0.6);
border-left-color: white;

View file

@ -114,6 +114,9 @@ $ios-border-color: rgba(0,0,0,0.1);
}
.quoted-message {
border-top-left-radius: 15px;
border-top-right-radius: 15px;
// Not ideal, but necessary to override the specificity of the android theme color
// classes used in conversations.scss
background-color: white !important;
@ -184,6 +187,28 @@ $ios-border-color: rgba(0,0,0,0.1);
}
}
.close-container {
flex: initial;
min-width: 32px;
width: 32px;
height: 48px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
-webkit-mask: none;
background: none;
.close-button {
height: 20px;
width: 20px;
@include color-svg('../images/close-circle.svg', $grey_l4);
}
}
.from-me {
.primary {
.text,
@ -218,6 +243,56 @@ $ios-border-color: rgba(0,0,0,0.1);
background-color: lightgray !important;
}
.bottom-bar {
.quote-wrapper {
margin-right: 0px;
margin-bottom: 15px;
}
.quoted-message {
border-top-left-radius: 0px;
border-top-right-radius: 0px;
background: none !important;
border: none !important;
.primary {
padding: 0px;
.ios-label {
color: $grey_l4;
}
}
.icon-container {
height: 48px;
width: 48px;
min-width: 48px;
.circle-background {
left: 6px;
right: 6px;
top: 6px;
bottom: 6px;
background-color: $blue !important;
}
.icon {
left: 12px;
right: 12px;
top: 12px;
bottom: 12px;
}
.inner {
padding: 0px;
height: 48px;
}
}
}
}
.attachments .bubbled {
border-radius: 15px;

View file

@ -893,3 +893,132 @@ const View = Whisper.MessageView;
/>
</util.ConversationContext>
```
### In bottom bar
#### Plain text
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
i18n={window.i18n}
/>
</div>
</div>
```
#### With an icon
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
i18n={window.i18n}
attachments={[{
contentType: 'image/jpeg',
fileName: 'llama.jpg',
}]}
/>
</div>
</div>
```
#### With an image
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
i18n={window.i18n}
attachments={[{
contentType: 'image/gif',
fileName: 'llama.gif',
thumbnail: {
objectUrl: util.gifObjectUrl
},
}]}
/>
</div>
</div>
```
#### With a close button
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
onClose={() => console.log('Close was clicked!')}
i18n={window.i18n}
/>
</div>
</div>
```
#### With a close button and icon
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
onClose={() => console.log('Close was clicked!')}
i18n={window.i18n}
attachments={[{
contentType: 'image/jpeg',
fileName: 'llama.jpg',
}]}
/>
</div>
</div>
```
#### With a close button and image
```jsx
<div className={util.theme}>
<div className="bottom-bar">
<Quote
text="How many ferrets do you have?"
authorColor="blue"
authorTitle={util.ourNumber}
authorProfileName="Mr. Blue"
id={Date.now() - 1000}
onClose={() => console.log('Close was clicked!')}
i18n={window.i18n}
attachments={[{
contentType: 'image/gif',
fileName: 'llama.gif',
thumbnail: {
objectUrl: util.gifObjectUrl
},
}]}
/>
</div>
</div>
```

View file

@ -14,6 +14,7 @@ interface Props {
isFromMe: string;
isIncoming: boolean;
onClick?: () => void;
onClose?: () => void;
text: string;
}
@ -153,6 +154,22 @@ export class Quote extends React.Component<Props, {}> {
return <div className="ios-label">{label}</div>;
}
public renderClose() {
const { onClose } = this.props;
if (!onClose) {
return null;
}
// We need the container to give us the flexibility to implement the iOS design.
// We put the onClick on both because the Android theme juse uses close-container
return (
<div className="close-container" onClick={onClose}>
<div className="close-button" onClick={onClose}></div>
</div>
);
}
public render() {
const {
authorTitle,
@ -186,6 +203,7 @@ export class Quote extends React.Component<Props, {}> {
{this.renderText()}
</div>
{this.renderIconContainer()}
{this.renderClose()}
</div>
);
}