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:
parent
e66f9faf33
commit
c71dcf0139
6 changed files with 314 additions and 2 deletions
1
images/close-circle.svg
Normal file
1
images/close-circle.svg
Normal 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 |
|
@ -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…
Reference in a new issue