Fixed examples in Quote.md, rough Android visuals
This commit is contained in:
parent
6653123671
commit
21bf02c94d
15 changed files with 408 additions and 59 deletions
|
@ -428,6 +428,18 @@
|
||||||
"selectAContact": {
|
"selectAContact": {
|
||||||
"message": "Select a contact or group to start chatting."
|
"message": "Select a contact or group to start chatting."
|
||||||
},
|
},
|
||||||
|
"audio": {
|
||||||
|
"message": "Audio",
|
||||||
|
"description": "Shown in a quotation of a message containing an audio attachment if no text was originally provided with that attachment"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"message": "Video",
|
||||||
|
"description": "Shown in a quotation of a message containing a video if no text was originally provided with that video"
|
||||||
|
},
|
||||||
|
"photo": {
|
||||||
|
"message": "Photo",
|
||||||
|
"description": "Shown in a quotation of a message containing a photo if no text was originally provided with that image"
|
||||||
|
},
|
||||||
"ok": {
|
"ok": {
|
||||||
"message": "OK"
|
"message": "OK"
|
||||||
},
|
},
|
||||||
|
|
|
@ -278,6 +278,7 @@
|
||||||
{{ /profileName }}
|
{{ /profileName }}
|
||||||
</div>
|
</div>
|
||||||
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
||||||
|
<div class='quote-wrapper'></div>
|
||||||
<div class='attachments'></div>
|
<div class='attachments'></div>
|
||||||
<p class='content' dir='auto'>
|
<p class='content' dir='auto'>
|
||||||
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
||||||
|
|
1
images/play.svg
Normal file
1
images/play.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="M8,5.14V19.14L19,12.14L8,5.14Z" /></svg>
|
After Width: | Height: | Size: 325 B |
|
@ -1,2 +1,10 @@
|
||||||
exports.isJPEG = mimeType =>
|
exports.isJPEG = mimeType =>
|
||||||
mimeType === 'image/jpeg';
|
mimeType === 'image/jpeg';
|
||||||
|
|
||||||
|
exports.isVideo = mimeType =>
|
||||||
|
mimeType.startsWith('video/') && mimeType !== 'video/wmv';
|
||||||
|
|
||||||
|
exports.isImage = mimeType =>
|
||||||
|
mimeType.startsWith('image/') && mimeType !== 'image/tiff';
|
||||||
|
|
||||||
|
exports.isAudio = mimeType => mimeType.startsWith('audio/');
|
||||||
|
|
|
@ -136,7 +136,8 @@
|
||||||
return this.model.contentType.startsWith('audio/');
|
return this.model.contentType.startsWith('audio/');
|
||||||
},
|
},
|
||||||
isVideo() {
|
isVideo() {
|
||||||
return this.model.contentType.startsWith('video/');
|
const type = this.model.contentType;
|
||||||
|
return type.startsWith('video/') && type !== 'image/wmv';
|
||||||
},
|
},
|
||||||
isImage() {
|
isImage() {
|
||||||
const type = this.model.contentType;
|
const type = this.model.contentType;
|
||||||
|
|
|
@ -235,7 +235,6 @@
|
||||||
// Failsafe: if in the background, animation events don't fire
|
// Failsafe: if in the background, animation events don't fire
|
||||||
setTimeout(this.remove.bind(this), 1000);
|
setTimeout(this.remove.bind(this), 1000);
|
||||||
},
|
},
|
||||||
/* jshint ignore:start */
|
|
||||||
onUnload() {
|
onUnload() {
|
||||||
if (this.avatarView) {
|
if (this.avatarView) {
|
||||||
this.avatarView.remove();
|
this.avatarView.remove();
|
||||||
|
@ -252,6 +251,9 @@
|
||||||
if (this.timeStampView) {
|
if (this.timeStampView) {
|
||||||
this.timeStampView.remove();
|
this.timeStampView.remove();
|
||||||
}
|
}
|
||||||
|
if (this.replyView) {
|
||||||
|
this.replyView.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: We have to do this in the background (`then` instead of `await`)
|
// NOTE: We have to do this in the background (`then` instead of `await`)
|
||||||
// as our tests rely on `onUnload` synchronously removing the view from
|
// as our tests rely on `onUnload` synchronously removing the view from
|
||||||
|
@ -265,7 +267,6 @@
|
||||||
|
|
||||||
this.remove();
|
this.remove();
|
||||||
},
|
},
|
||||||
/* jshint ignore:end */
|
|
||||||
onDestroy() {
|
onDestroy() {
|
||||||
if (this.$el.hasClass('expired')) {
|
if (this.$el.hasClass('expired')) {
|
||||||
return;
|
return;
|
||||||
|
@ -359,6 +360,53 @@
|
||||||
this.timerView.setElement(this.$('.timer'));
|
this.timerView.setElement(this.$('.timer'));
|
||||||
this.timerView.update();
|
this.timerView.update();
|
||||||
},
|
},
|
||||||
|
renderReply() {
|
||||||
|
const VOICE_MESSAGE_FLAG =
|
||||||
|
textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE;
|
||||||
|
function addVoiceMessageFlag(attachment) {
|
||||||
|
return Object.assign({}, attachment, {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
isVoiceMessage: attachment.flags & VOICE_MESSAGE_FLAG,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function getObjectUrl(attachment) {
|
||||||
|
if (!attachment || attachment.objectUrl) {
|
||||||
|
return attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([attachment.data], {
|
||||||
|
type: attachment.contentType,
|
||||||
|
});
|
||||||
|
return Object.assign({}, attachment, {
|
||||||
|
objectUrl: URL.createObjectURL(blob),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function processAttachment(attachment) {
|
||||||
|
return getObjectUrl(addVoiceMessageFlag(attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
const quote = this.model.get('quote');
|
||||||
|
if (!quote) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
authorName: 'someone',
|
||||||
|
authorColor: 'indigo',
|
||||||
|
text: quote.text,
|
||||||
|
attachments: quote.attachments && quote.attachments.map(processAttachment),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.replyView) {
|
||||||
|
this.replyView = new Whisper.ReactWrapperView({
|
||||||
|
el: this.$('.quote-wrapper'),
|
||||||
|
Component: window.Signal.Components.Quote,
|
||||||
|
props,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.replyView.update(props);
|
||||||
|
}
|
||||||
|
},
|
||||||
isImageWithoutCaption() {
|
isImageWithoutCaption() {
|
||||||
const attachments = this.model.get('attachments');
|
const attachments = this.model.get('attachments');
|
||||||
const body = this.model.get('body');
|
const body = this.model.get('body');
|
||||||
|
@ -406,6 +454,7 @@
|
||||||
this.renderRead();
|
this.renderRead();
|
||||||
this.renderErrors();
|
this.renderErrors();
|
||||||
this.renderExpiring();
|
this.renderExpiring();
|
||||||
|
this.renderReply();
|
||||||
|
|
||||||
|
|
||||||
// NOTE: We have to do this in the background (`then` instead of `await`)
|
// NOTE: We have to do this in the background (`then` instead of `await`)
|
||||||
|
|
|
@ -94,6 +94,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.1.2",
|
"@types/chai": "^4.1.2",
|
||||||
|
"@types/classnames": "^2.2.3",
|
||||||
"@types/lodash": "^4.14.106",
|
"@types/lodash": "^4.14.106",
|
||||||
"@types/mocha": "^5.0.0",
|
"@types/mocha": "^5.0.0",
|
||||||
"@types/qs": "^6.5.1",
|
"@types/qs": "^6.5.1",
|
||||||
|
|
|
@ -450,6 +450,75 @@ span.status {
|
||||||
max-width: calc(100% - 45px - #{$error-icon-size}); // avatar size + padding + error-icon size
|
max-width: calc(100% - 45px - #{$error-icon-size}); // avatar size + padding + error-icon size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
@include message-replies-colors;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: #eee;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-top: $android-bubble-quote-padding - $android-bubble-padding-vertical;
|
||||||
|
margin-right: $android-bubble-quote-padding - $android-bubble-padding-horizontal;
|
||||||
|
margin-left: $android-bubble-quote-padding - $android-bubble-padding-horizontal;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
// Accent color border:
|
||||||
|
border-left-width: 3;
|
||||||
|
border-left-style: solid;
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-label {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-container {
|
||||||
|
flex: initial;
|
||||||
|
min-width: 48px;
|
||||||
|
@include aspect-ratio(1, 1);
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
border: 1px red solid;
|
||||||
|
max-height: 48px;
|
||||||
|
max-width: 48px;
|
||||||
|
|
||||||
|
&.file {
|
||||||
|
@include color-svg('../images/file.svg', $grey_d);
|
||||||
|
}
|
||||||
|
&.microphone {
|
||||||
|
@include color-svg('../images/microphone.svg', $grey_d);
|
||||||
|
}
|
||||||
|
&.play {
|
||||||
|
@include color-svg('../images/play.svg', $grey_d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
@mixin aspect-ratio($width, $height) {
|
||||||
|
position: relative;
|
||||||
|
&:before {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
padding-top: ($height / $width) * 100%;
|
||||||
|
}
|
||||||
|
> .inner {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin color-svg($svg, $color) {
|
@mixin color-svg($svg, $color) {
|
||||||
-webkit-mask: url($svg) no-repeat center;
|
-webkit-mask: url($svg) no-repeat center;
|
||||||
-webkit-mask-size: 100%;
|
-webkit-mask-size: 100%;
|
||||||
|
@ -53,6 +70,47 @@
|
||||||
&.grey { background-color: #666666 ; }
|
&.grey { background-color: #666666 ; }
|
||||||
&.default { background-color: $blue ; }
|
&.default { background-color: $blue ; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Deduplicate these! Can SASS functions generate property names?
|
||||||
|
@mixin message-replies-colors {
|
||||||
|
&.red { border-left-color: $material_red ; }
|
||||||
|
&.pink { border-left-color: $material_pink ; }
|
||||||
|
&.purple { border-left-color: $material_purple ; }
|
||||||
|
&.deep_purple { border-left-color: $material_deep_purple ; }
|
||||||
|
&.indigo { border-left-color: $material_indigo ; }
|
||||||
|
&.blue { border-left-color: $material_blue ; }
|
||||||
|
&.light_blue { border-left-color: $material_light_blue ; }
|
||||||
|
&.cyan { border-left-color: $material_cyan ; }
|
||||||
|
&.teal { border-left-color: $material_teal ; }
|
||||||
|
&.green { border-left-color: $material_green ; }
|
||||||
|
&.light_green { border-left-color: $material_light_green ; }
|
||||||
|
&.orange { border-left-color: $material_orange ; }
|
||||||
|
&.deep_orange { border-left-color: $material_deep_orange ; }
|
||||||
|
&.amber { border-left-color: $material_amber ; }
|
||||||
|
&.blue_grey { border-left-color: $material_blue_grey ; }
|
||||||
|
&.grey { border-left-color: #999999 ; }
|
||||||
|
&.default { border-left-color: $blue ; }
|
||||||
|
}
|
||||||
|
@mixin dark-message-replies-colors {
|
||||||
|
&.red { border-left-color: $dark_material_red ; }
|
||||||
|
&.pink { border-left-color: $dark_material_pink ; }
|
||||||
|
&.purple { border-left-color: $dark_material_purple ; }
|
||||||
|
&.deep_purple { border-left-color: $dark_material_deep_purple ; }
|
||||||
|
&.indigo { border-left-color: $dark_material_indigo ; }
|
||||||
|
&.blue { border-left-color: $dark_material_blue ; }
|
||||||
|
&.light_blue { border-left-color: $dark_material_light_blue ; }
|
||||||
|
&.cyan { border-left-color: $dark_material_cyan ; }
|
||||||
|
&.teal { border-left-color: $dark_material_teal ; }
|
||||||
|
&.green { border-left-color: $dark_material_green ; }
|
||||||
|
&.light_green { border-left-color: $dark_material_light_green ; }
|
||||||
|
&.orange { border-left-color: $dark_material_orange ; }
|
||||||
|
&.deep_orange { border-left-color: $dark_material_deep_orange ; }
|
||||||
|
&.amber { border-left-color: $dark_material_amber ; }
|
||||||
|
&.blue_grey { border-left-color: $dark_material_blue_grey ; }
|
||||||
|
&.grey { border-left-color: #666666 ; }
|
||||||
|
&.default { border-left-color: $blue ; }
|
||||||
|
}
|
||||||
|
|
||||||
@mixin invert-text-color {
|
@mixin invert-text-color {
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
|
|
|
@ -82,3 +82,8 @@ $dark_material_orange: #F57C00;
|
||||||
$dark_material_deep_orange: #E64A19;
|
$dark_material_deep_orange: #E64A19;
|
||||||
$dark_material_amber: #FFA000;
|
$dark_material_amber: #FFA000;
|
||||||
$dark_material_blue_grey: #455A64;
|
$dark_material_blue_grey: #455A64;
|
||||||
|
|
||||||
|
// Android
|
||||||
|
$android-bubble-padding-horizontal: 12px;
|
||||||
|
$android-bubble-padding-vertical: 9px;
|
||||||
|
$android-bubble-quote-padding: 4px;
|
||||||
|
|
|
@ -213,6 +213,7 @@
|
||||||
{{ /profileName }}
|
{{ /profileName }}
|
||||||
</div>
|
</div>
|
||||||
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
||||||
|
<div class='quote-wrapper'></div>
|
||||||
<div class='attachments'></div>
|
<div class='attachments'></div>
|
||||||
<p class='content' dir='auto'>
|
<p class='content' dir='auto'>
|
||||||
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
||||||
|
|
|
@ -33,6 +33,7 @@ window.Whisper.View.Templates = {
|
||||||
{{ /profileName }}
|
{{ /profileName }}
|
||||||
</div>
|
</div>
|
||||||
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
<div class='inner-bubble {{ innerBubbleClasses }}'>
|
||||||
|
<div class='quote-wrapper'></div>
|
||||||
<div class='attachments'></div>
|
<div class='attachments'></div>
|
||||||
<p class='content' dir='auto'>
|
<p class='content' dir='auto'>
|
||||||
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
{{ #message }}<div class='body'>{{ message }}</div>{{ /message }}
|
||||||
|
|
|
@ -45,14 +45,16 @@ const outgoing = new Whisper.Message({
|
||||||
text: 'I am pretty confused about Pi.',
|
text: 'I am pretty confused about Pi.',
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'image/gif',
|
{
|
||||||
fileName: 'pi.gif',
|
|
||||||
thumbnail: {
|
|
||||||
contentType: 'image/gif',
|
contentType: 'image/gif',
|
||||||
data: util.gif,
|
fileName: 'pi.gif',
|
||||||
}
|
thumbnail: {
|
||||||
}
|
contentType: 'image/gif',
|
||||||
|
data: util.gif,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -85,14 +87,16 @@ const outgoing = new Whisper.Message({
|
||||||
quote: {
|
quote: {
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'image/gif',
|
{
|
||||||
fileName: 'pi.gif',
|
|
||||||
thumbnail: {
|
|
||||||
contentType: 'image/gif',
|
contentType: 'image/gif',
|
||||||
data: util.gif,
|
fileName: 'pi.gif',
|
||||||
}
|
thumbnail: {
|
||||||
}
|
contentType: 'image/gif',
|
||||||
|
data: util.gif,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -126,14 +130,16 @@ const outgoing = new Whisper.Message({
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
text: 'Check out this video I found!',
|
text: 'Check out this video I found!',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'video/mp4',
|
{
|
||||||
fileName: 'freezing_bubble.mp4',
|
contentType: 'video/mp4',
|
||||||
thumbnail: {
|
fileName: 'freezing_bubble.mp4',
|
||||||
contentType: 'image/gif',
|
thumbnail: {
|
||||||
data: util.gif,
|
contentType: 'image/gif',
|
||||||
}
|
data: util.gif,
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -166,14 +172,16 @@ const outgoing = new Whisper.Message({
|
||||||
quote: {
|
quote: {
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'video/mp4',
|
{
|
||||||
fileName: 'freezing_bubble.mp4',
|
contentType: 'video/mp4',
|
||||||
thumbnail: {
|
fileName: 'freezing_bubble.mp4',
|
||||||
contentType: 'image/gif',
|
thumbnail: {
|
||||||
data: util.gif,
|
contentType: 'image/gif',
|
||||||
}
|
data: util.gif,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -207,10 +215,12 @@ const outgoing = new Whisper.Message({
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
text: 'Check out this beautiful song!',
|
text: 'Check out this beautiful song!',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'audio/mp3',
|
{
|
||||||
fileName: 'agnus_dei.mp4',
|
contentType: 'audio/mp3',
|
||||||
}
|
fileName: 'agnus_dei.mp4',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -243,10 +253,12 @@ const outgoing = new Whisper.Message({
|
||||||
quote: {
|
quote: {
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'audio/mp3',
|
{
|
||||||
fileName: 'agnus_dei.mp4',
|
contentType: 'audio/mp3',
|
||||||
}
|
fileName: 'agnus_dei.mp4',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -279,12 +291,14 @@ const outgoing = new Whisper.Message({
|
||||||
quote: {
|
quote: {
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
// proposed as of afternoon of 4/6 in Quoted Replies group
|
{
|
||||||
flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
// proposed as of afternoon of 4/6 in Quoted Replies group
|
||||||
contentType: 'audio/mp3',
|
flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE,
|
||||||
fileName: 'agnus_dei.mp4',
|
contentType: 'audio/mp3',
|
||||||
}
|
fileName: 'agnus_dei.mp4',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -318,10 +332,12 @@ const outgoing = new Whisper.Message({
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
text: 'This is my manifesto. Tell me what you think!',
|
text: 'This is my manifesto. Tell me what you think!',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'text/plain',
|
{
|
||||||
fileName: 'lorum_ipsum.txt',
|
contentType: 'text/plain',
|
||||||
}
|
fileName: 'lorum_ipsum.txt',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
@ -354,10 +370,12 @@ const outgoing = new Whisper.Message({
|
||||||
quote: {
|
quote: {
|
||||||
author: '+12025550100',
|
author: '+12025550100',
|
||||||
id: Date.now() - 1000,
|
id: Date.now() - 1000,
|
||||||
attachments: {
|
attachments: [
|
||||||
contentType: 'text/plain',
|
{
|
||||||
fileName: 'lorum_ipsum.txt',
|
contentType: 'text/plain',
|
||||||
}
|
fileName: 'lorum_ipsum.txt',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||||
|
|
|
@ -1,14 +1,134 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import Mime from '../../../js/modules/types/mime';
|
||||||
|
|
||||||
|
|
||||||
interface Props { name: string; }
|
interface Props {
|
||||||
|
i18n: (key: string, values?: Array<string>) => string;
|
||||||
|
authorName: string;
|
||||||
|
authorColor: string;
|
||||||
|
attachments: Array<QuotedAttachment>;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface State { count: number; }
|
interface QuotedAttachment {
|
||||||
|
fileName: string;
|
||||||
|
contentType: string;
|
||||||
|
isVoiceMessage: boolean;
|
||||||
|
objectUrl: string;
|
||||||
|
thumbnail: {
|
||||||
|
contentType: string;
|
||||||
|
data: ArrayBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateQuote(quote: Props): boolean {
|
||||||
|
if (quote.text) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quote.attachments && quote.attachments.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContentType(attachments: Array<QuotedAttachment>): string | null {
|
||||||
|
if (!attachments || attachments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = attachments[0];
|
||||||
|
return first.contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Quote extends React.Component<Props, {}> {
|
||||||
|
public renderIcon(first: QuotedAttachment) {
|
||||||
|
const contentType = first.contentType;
|
||||||
|
const objectUrl = first.objectUrl;
|
||||||
|
|
||||||
|
if (Mime.isVideo(contentType)) {
|
||||||
|
// Render play icon on top of thumbnail
|
||||||
|
// We'd have to generate our own thumbnail from a local video??
|
||||||
|
return <div className='inner play'>Video</div>;
|
||||||
|
} else if (Mime.isImage(contentType)) {
|
||||||
|
if (objectUrl) {
|
||||||
|
return <div className='inner'><img src={objectUrl} /></div>;
|
||||||
|
} else {
|
||||||
|
return <div className='inner'>Loading Widget</div>
|
||||||
|
}
|
||||||
|
} else if (Mime.isAudio(contentType)) {
|
||||||
|
// Show microphone inner in circle
|
||||||
|
return <div className='inner microphone'>Audio</div>;
|
||||||
|
} else {
|
||||||
|
// Show file icon
|
||||||
|
return <div className='inner file'>File</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderIconContainer() {
|
||||||
|
const { attachments } = this.props;
|
||||||
|
|
||||||
|
if (!attachments || attachments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = attachments[0];
|
||||||
|
|
||||||
|
return <div className='icon-container'>
|
||||||
|
{this.renderIcon(first)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderText() {
|
||||||
|
const { i18n, text, attachments } = this.props;
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
return <div className='text'>{text}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attachments || attachments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = getContentType(attachments);
|
||||||
|
const first = attachments[0];
|
||||||
|
const fileName = first.fileName;
|
||||||
|
|
||||||
|
console.log(contentType);
|
||||||
|
|
||||||
|
if (Mime.isVideo(contentType)) {
|
||||||
|
return <div className='type-label'>{i18n('video')}</div>;
|
||||||
|
} else if (Mime.isImage(contentType)) {
|
||||||
|
return <div className='type-label'>{i18n('photo')}</div>;
|
||||||
|
} else if (Mime.isAudio(contentType) && first.isVoiceMessage) {
|
||||||
|
return <div className='type-label'>{i18n('voiceMessage')}</div>;
|
||||||
|
} else if (Mime.isAudio(contentType)) {
|
||||||
|
console.log(first);
|
||||||
|
return <div className='type-label'>{i18n('audio')}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className='filename-label'>{fileName}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
export class Reply extends React.Component<Props, State> {
|
|
||||||
public render() {
|
public render() {
|
||||||
|
const { authorName, authorColor } = this.props;
|
||||||
|
|
||||||
|
if (!validateQuote(this.props)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>Placeholder</div>
|
<div className={classnames(authorColor, 'quote')} >
|
||||||
|
<div className="primary">
|
||||||
|
<div className="author">{authorName}</div>
|
||||||
|
{this.renderText()}
|
||||||
|
</div>
|
||||||
|
{this.renderIconContainer()}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,10 @@
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21"
|
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21"
|
||||||
|
|
||||||
|
"@types/classnames@^2.2.3":
|
||||||
|
version "2.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5"
|
||||||
|
|
||||||
"@types/lodash@^4.14.106":
|
"@types/lodash@^4.14.106":
|
||||||
version "4.14.106"
|
version "4.14.106"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
||||||
|
|
Loading…
Reference in a new issue