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.
pull/1/head
Scott Nonnenberg 7 years ago
parent e66f9faf33
commit c71dcf0139
No known key found for this signature in database
GPG Key ID: 5F82280C35134661

@ -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

@ -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);

@ -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;

@ -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;

@ -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>
```

@ -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>
);
}

Loading…
Cancel
Save