In iOS theme, join attachment bubble with caption bubble
This commit is contained in:
		
					parent
					
						
							
								3a76c3c86e
							
						
					
				
			
			
				commit
				
					
						ae043bf239
					
				
			
		
					 9 changed files with 246 additions and 97 deletions
				
			
		|  | @ -277,10 +277,12 @@ | ||||||
|               <span class='profileName'>{{ profileName }} </span> |               <span class='profileName'>{{ profileName }} </span> | ||||||
|             {{ /profileName }} |             {{ /profileName }} | ||||||
|           </div> |           </div> | ||||||
|           <div class='attachments'></div> |           <div class='inner-bubble {{ innerBubbleClasses }}'> | ||||||
|           <p class='content' dir='auto'> |             <div class='attachments'></div> | ||||||
|             {{ #message }}<span class='body'>{{ message }}</span>{{ /message }} |             <p class='content' dir='auto'> | ||||||
|           </p> |               {{ #message }}<div class='body'>{{ message }}</div>{{ /message }} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|           <div class='meta'> |           <div class='meta'> | ||||||
|             <span class='timestamp' data-timestamp={{ timestamp }}></span> |             <span class='timestamp' data-timestamp={{ timestamp }}></span> | ||||||
|             <span class='status hide'></span> |             <span class='status hide'></span> | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ | ||||||
|   ]; |   ]; | ||||||
| 
 | 
 | ||||||
|   Whisper.AttachmentView = Backbone.View.extend({ |   Whisper.AttachmentView = Backbone.View.extend({ | ||||||
|     tagName: 'span', |     tagName: 'div', | ||||||
|     className() { |     className() { | ||||||
|       if (this.isImage()) { |       if (this.isImage()) { | ||||||
|         return 'attachment'; |         return 'attachment'; | ||||||
|  |  | ||||||
|  | @ -355,6 +355,24 @@ | ||||||
|             this.timerView.setElement(this.$('.timer')); |             this.timerView.setElement(this.$('.timer')); | ||||||
|             this.timerView.update(); |             this.timerView.update(); | ||||||
|         }, |         }, | ||||||
|  |         isImageWithoutCaption: function() { | ||||||
|  |             var attachments = this.model.get('attachments'); | ||||||
|  |             var body = this.model.get('body'); | ||||||
|  |             if (!attachments || attachments.length === 0) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (body && body.trim()) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             var first = attachments[0]; | ||||||
|  |             if (first.contentType.startsWith('image/') && first.contentType !== 'image/tiff') { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return false; | ||||||
|  |         }, | ||||||
|         render: function() { |         render: function() { | ||||||
|             var contact = this.model.isIncoming() ? this.model.getContact() : null; |             var contact = this.model.isIncoming() ? this.model.getContact() : null; | ||||||
|             this.$el.html( |             this.$el.html( | ||||||
|  | @ -364,6 +382,7 @@ | ||||||
|                     sender: (contact && contact.getTitle()) || '', |                     sender: (contact && contact.getTitle()) || '', | ||||||
|                     avatar: (contact && contact.getAvatar()), |                     avatar: (contact && contact.getAvatar()), | ||||||
|                     profileName: (contact && contact.getProfileName()), |                     profileName: (contact && contact.getProfileName()), | ||||||
|  |                     innerBubbleClasses: this.isImageWithoutCaption() ? '' : 'with-tail', | ||||||
|                 }, this.render_partials()) |                 }, this.render_partials()) | ||||||
|             ); |             ); | ||||||
|             this.timeStampView.setElement(this.$('.timestamp')); |             this.timeStampView.setElement(this.$('.timestamp')); | ||||||
|  |  | ||||||
|  | @ -451,6 +451,7 @@ span.status { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .body { |     .body { | ||||||
|  |       margin-top: 0.5em; | ||||||
|       white-space: pre-wrap; |       white-space: pre-wrap; | ||||||
| 
 | 
 | ||||||
|       a { |       a { | ||||||
|  | @ -591,6 +592,7 @@ span.status { | ||||||
|       position: relative; |       position: relative; | ||||||
|       padding: 5px; |       padding: 5px; | ||||||
|       padding-right: 10px; |       padding-right: 10px; | ||||||
|  |       padding-bottom: 0px; | ||||||
| 
 | 
 | ||||||
|       cursor: pointer; |       cursor: pointer; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -109,11 +109,33 @@ $ios-border-color: rgba(0,0,0,0.1); | ||||||
| 
 | 
 | ||||||
|   .attachments .bubbled { |   .attachments .bubbled { | ||||||
|     border-radius: 15px; |     border-radius: 15px; | ||||||
|     margin-bottom: 0.25em; |  | ||||||
| 
 | 
 | ||||||
|     padding: 10px; |     padding: 10px; | ||||||
|  |     padding-top: 0px; | ||||||
|  |     padding-bottom: 5px; | ||||||
| 
 | 
 | ||||||
|     position: relative; |     position: relative; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .inner-bubble { | ||||||
|  |     border-radius: 15px; | ||||||
|  |     margin-bottom: 5px; | ||||||
|  | 
 | ||||||
|  |     .body { | ||||||
|  |       margin-top: 0; | ||||||
|  |       display: inline-block; | ||||||
|  |       padding: 10px; | ||||||
|  |       position: relative; | ||||||
|  |       word-break: break-word; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     .attachments img { | ||||||
|  |       border-radius: 15px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .inner-bubble.with-tail { | ||||||
|  |     position: relative; | ||||||
| 
 | 
 | ||||||
|     &:before, &:after { |     &:before, &:after { | ||||||
|       content: ''; |       content: ''; | ||||||
|  | @ -135,51 +157,27 @@ $ios-border-color: rgba(0,0,0,0.1); | ||||||
|       bottom: -3px; |       bottom: -3px; | ||||||
|       background: #eee; |       background: #eee; | ||||||
|     } |     } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   .bubble { |  | ||||||
|     .content { |  | ||||||
|       margin-bottom: 5px; |  | ||||||
|       .body { |  | ||||||
|         display: inline-block; |  | ||||||
|         padding: 10px; |  | ||||||
|         position: relative; |  | ||||||
|         word-break: break-word; |  | ||||||
| 
 |  | ||||||
|         &:before, &:after { |  | ||||||
|           content: ''; |  | ||||||
|           display: block; |  | ||||||
|           border-radius: 20px; |  | ||||||
|           position: absolute; |  | ||||||
|           width: 10px; |  | ||||||
|         } |  | ||||||
|         &:before { |  | ||||||
|           right: -1px; |  | ||||||
|           bottom: -3px; |  | ||||||
|           height: 10px; |  | ||||||
|           border-radius: 20px; |  | ||||||
|           background: $blue; |  | ||||||
|         } |  | ||||||
|         &:after { |  | ||||||
|           height: 11px; |  | ||||||
|           right: -6px; |  | ||||||
|           bottom: -3px; |  | ||||||
|           background: #eee; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     .content, .attachments img { |  | ||||||
|       border-radius: 15px; |  | ||||||
|     } |  | ||||||
|     .attachments img { |     .attachments img { | ||||||
|       background-color: white; |       border-bottom-left-radius: 0px; | ||||||
|     } |       border-bottom-right-radius: 0px; | ||||||
|     .meta { |  | ||||||
|       clear: both; |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .incoming .bubbled { |   .meta { | ||||||
|  |     clear: both; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .outgoing .inner-bubble.with-tail { | ||||||
|  |     background-color: $blue; | ||||||
|  |     max-width: 100%; | ||||||
|  |     &, .body, a { | ||||||
|  |       @include invert-text-color; | ||||||
|  |     } | ||||||
|  |     float: right; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .incoming .inner-bubble.with-tail { | ||||||
|     background-color: white; |     background-color: white; | ||||||
|     color: black; |     color: black; | ||||||
|     float: left; |     float: left; | ||||||
|  | @ -194,31 +192,6 @@ $ios-border-color: rgba(0,0,0,0.1); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .incoming .content { |  | ||||||
|     background-color: white; |  | ||||||
|     color: black; |  | ||||||
|     float: left; |  | ||||||
|     .body { |  | ||||||
|       &:before { |  | ||||||
|         left: -1px; |  | ||||||
|         background-color: white; |  | ||||||
|       } |  | ||||||
|       &:after { |  | ||||||
|         left: -6px; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   .outgoing { |  | ||||||
|     .content, .attachments .bubbled { |  | ||||||
|       background-color: $blue; |  | ||||||
|       max-width: 100%; |  | ||||||
|       &, .body, a { |  | ||||||
|         @include invert-text-color; |  | ||||||
|       } |  | ||||||
|       float: right; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   .outgoing .attachments .fileView .icon { |   .outgoing .attachments .fileView .icon { | ||||||
|     @include color-svg('../images/file.svg', white); |     @include color-svg('../images/file.svg', white); | ||||||
|     &.audio { |     &.audio { | ||||||
|  | @ -236,7 +209,6 @@ $ios-border-color: rgba(0,0,0,0.1); | ||||||
|     a { |     a { | ||||||
|       border-radius: 15px; |       border-radius: 15px; | ||||||
|     } |     } | ||||||
|     margin-bottom: 1px; |  | ||||||
|   } |   } | ||||||
|   .hourglass { |   .hourglass { | ||||||
|     @include hourglass(#999); |     @include hourglass(#999); | ||||||
|  |  | ||||||
|  | @ -206,14 +206,22 @@ | ||||||
|   <script type='text/x-tmpl-mustache' id='message'> |   <script type='text/x-tmpl-mustache' id='message'> | ||||||
|       {{> avatar }} |       {{> avatar }} | ||||||
|       <div class='bubble {{ avatar.color }}'> |       <div class='bubble {{ avatar.color }}'> | ||||||
|           <div class='sender' dir='auto'>{{ sender }}</div> |           <div class='sender' dir='auto'> | ||||||
|           <div class='attachments'></div> |             {{ sender }} | ||||||
|           <p class='content' dir='auto'> |             {{ #profileName }} | ||||||
|             {{ #message }}<span class='body'>{{ message }}</span>{{ /message }} |               <span class='profileName'>{{ profileName }} </span> | ||||||
|           </p> |             {{ /profileName }} | ||||||
|  |           </div> | ||||||
|  |           <div class='inner-bubble {{ innerBubbleClasses }}'> | ||||||
|  |             <div class='attachments'></div> | ||||||
|  |             <p class='content' dir='auto'> | ||||||
|  |               {{ #message }}<div class='body'>{{ message }}</div>{{ /message }} | ||||||
|  |             </p> | ||||||
|  |           </div> | ||||||
|           <div class='meta'> |           <div class='meta'> | ||||||
|             <span class='timestamp' data-timestamp={{ timestamp }}></span> |             <span class='timestamp' data-timestamp={{ timestamp }}></span> | ||||||
|             <span class='status hide'></span> |             <span class='status hide'></span> | ||||||
|  |             <span class='timer'></span> | ||||||
|           </div> |           </div> | ||||||
|       </div> |       </div> | ||||||
|   </script> |   </script> | ||||||
|  |  | ||||||
|  | @ -32,10 +32,12 @@ window.Whisper.View.Templates = { | ||||||
|             <span class='profileName'>{{ profileName }} </span> |             <span class='profileName'>{{ profileName }} </span> | ||||||
|           {{ /profileName }} |           {{ /profileName }} | ||||||
|         </div> |         </div> | ||||||
|         <div class='attachments'></div> |         <div class='inner-bubble {{ innerBubbleClasses }}'> | ||||||
|         <p class='content' dir='auto'> |           <div class='attachments'></div> | ||||||
|           {{ #message }}<span class='body'>{{ message }}</span>{{ /message }} |           <p class='content' dir='auto'> | ||||||
|         </p> |             {{ #message }}<div class='body'>{{ message }}</div>{{ /message }} | ||||||
|  |           </p> | ||||||
|  |         </div> | ||||||
|         <div class='meta'> |         <div class='meta'> | ||||||
|           <span class='timestamp' data-timestamp={{ timestamp }}></span> |           <span class='timestamp' data-timestamp={{ timestamp }}></span> | ||||||
|           <span class='status hide'></span> |           <span class='status hide'></span> | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| 
 | 
 | ||||||
| Placeholder: | Placeholder component: | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| <util.ConversationContext theme={util.theme}> | <util.ConversationContext theme={util.theme}> | ||||||
|  | @ -7,9 +7,36 @@ Placeholder: | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## With an attachment | ## MessageView (Backbone) | ||||||
| 
 | 
 | ||||||
| ### Image | ### Plain messages | ||||||
|  | 
 | ||||||
|  | ```jsx | ||||||
|  | const outgoing = new Whisper.Message({ | ||||||
|  |   type: 'outgoing', | ||||||
|  |   body: 'How are you doing this fine day?', | ||||||
|  |   sent_at: Date.now() - 18000, | ||||||
|  | }); | ||||||
|  | const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, { | ||||||
|  |   source: '+12025550100', | ||||||
|  |   type: 'incoming', | ||||||
|  | })); | ||||||
|  | const View = Whisper.MessageView; | ||||||
|  | <util.ConversationContext theme={util.theme}> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: incoming }} | ||||||
|  |   /> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: outgoing }} | ||||||
|  |   /> | ||||||
|  | </util.ConversationContext> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ### With an attachment | ||||||
|  | 
 | ||||||
|  | #### Image | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| const outgoing = new Whisper.Message({ | const outgoing = new Whisper.Message({ | ||||||
|  | @ -39,12 +66,41 @@ const View = Whisper.MessageView; | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Video | #### Image, no caption | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| const outgoing = new Whisper.Message({ | const outgoing = new Whisper.Message({ | ||||||
|   type: 'outgoing', |   type: 'outgoing', | ||||||
|   body: "Beatiful, isn't it?", |   sent_at: Date.now() - 18000000, | ||||||
|  |   attachments: [{ | ||||||
|  |     data: util.gif, | ||||||
|  |     fileName: 'pi.gif', | ||||||
|  |     contentType: 'image/gif', | ||||||
|  |   }], | ||||||
|  | }); | ||||||
|  | const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, { | ||||||
|  |   source: '+12025550100', | ||||||
|  |   type: 'incoming', | ||||||
|  | })); | ||||||
|  | const View = Whisper.MessageView; | ||||||
|  | <util.ConversationContext theme={util.theme}> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: incoming }} | ||||||
|  |   /> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: outgoing }} | ||||||
|  |   /> | ||||||
|  | </util.ConversationContext> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Video | ||||||
|  | 
 | ||||||
|  | ```jsx | ||||||
|  | const outgoing = new Whisper.Message({ | ||||||
|  |   type: 'outgoing', | ||||||
|  |   body: "Beautiful, isn't it?", | ||||||
|   sent_at: Date.now() - 10000, |   sent_at: Date.now() - 10000, | ||||||
|   attachments: [{ |   attachments: [{ | ||||||
|     data: util.mp4, |     data: util.mp4, | ||||||
|  | @ -69,7 +125,36 @@ const View = Whisper.MessageView; | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Audio | #### Video, no caption | ||||||
|  | 
 | ||||||
|  | ```jsx | ||||||
|  | const outgoing = new Whisper.Message({ | ||||||
|  |   type: 'outgoing', | ||||||
|  |   sent_at: Date.now() - 10000, | ||||||
|  |   attachments: [{ | ||||||
|  |     data: util.mp4, | ||||||
|  |     fileName: 'freezing_bubble.mp4', | ||||||
|  |     contentType: 'video/mp4', | ||||||
|  |   }], | ||||||
|  | }); | ||||||
|  | const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, { | ||||||
|  |   source: '+12025550100', | ||||||
|  |   type: 'incoming', | ||||||
|  | })); | ||||||
|  | const View = Whisper.MessageView; | ||||||
|  | <util.ConversationContext theme={util.theme}> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: incoming }} | ||||||
|  |   /> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: outgoing }} | ||||||
|  |   /> | ||||||
|  | </util.ConversationContext> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Audio | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| const outgoing = new Whisper.Message({ | const outgoing = new Whisper.Message({ | ||||||
|  | @ -99,12 +184,40 @@ const View = Whisper.MessageView; | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Voice message | #### Audio, no caption | ||||||
|  | 
 | ||||||
|  | ```jsx | ||||||
|  | const outgoing = new Whisper.Message({ | ||||||
|  |   type: 'outgoing', | ||||||
|  |   sent_at: Date.now() - 15000, | ||||||
|  |   attachments: [{ | ||||||
|  |     data: util.mp3, | ||||||
|  |     fileName: 'agnus_dei.mp3', | ||||||
|  |     contentType: 'audio/mp3', | ||||||
|  |   }], | ||||||
|  | }); | ||||||
|  | const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, { | ||||||
|  |   source: '+12025550100', | ||||||
|  |   type: 'incoming', | ||||||
|  | })); | ||||||
|  | const View = Whisper.MessageView; | ||||||
|  | <util.ConversationContext theme={util.theme}> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: incoming }} | ||||||
|  |   /> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: outgoing }} | ||||||
|  |   /> | ||||||
|  | </util.ConversationContext> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #### Voice message | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| const outgoing = new Whisper.Message({ | const outgoing = new Whisper.Message({ | ||||||
|   type: 'outgoing', |   type: 'outgoing', | ||||||
|   body: 'This is a nice song', |  | ||||||
|   sent_at: Date.now() - 15000, |   sent_at: Date.now() - 15000, | ||||||
|   attachments: [{ |   attachments: [{ | ||||||
|     flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE, |     flags: textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE, | ||||||
|  | @ -130,7 +243,7 @@ const View = Whisper.MessageView; | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Other file type | #### Other file type | ||||||
| 
 | 
 | ||||||
| ```jsx | ```jsx | ||||||
| const outgoing = new Whisper.Message({ | const outgoing = new Whisper.Message({ | ||||||
|  | @ -159,3 +272,32 @@ const View = Whisper.MessageView; | ||||||
|   /> |   /> | ||||||
| </util.ConversationContext> | </util.ConversationContext> | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | #### Other file type, no caption | ||||||
|  | 
 | ||||||
|  | ```jsx | ||||||
|  | const outgoing = new Whisper.Message({ | ||||||
|  |   type: 'outgoing', | ||||||
|  |   sent_at: Date.now() - 15000, | ||||||
|  |   attachments: [{ | ||||||
|  |     data: util.txt, | ||||||
|  |     fileName: 'lorum_ipsum.txt', | ||||||
|  |     contentType: 'text/plain', | ||||||
|  |   }], | ||||||
|  | }); | ||||||
|  | const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, { | ||||||
|  |   source: '+12025550100', | ||||||
|  |   type: 'incoming', | ||||||
|  | })); | ||||||
|  | const View = Whisper.MessageView; | ||||||
|  | <util.ConversationContext theme={util.theme}> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: incoming }} | ||||||
|  |   /> | ||||||
|  |   <util.BackboneWrapper | ||||||
|  |     View={View} | ||||||
|  |     options={{ model: outgoing }} | ||||||
|  |   /> | ||||||
|  | </util.ConversationContext> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import React from 'react'; | ||||||
| /** | /** | ||||||
|  * A placeholder Message component for now, giving the structure of a plain message with |  * A placeholder Message component for now, giving the structure of a plain message with | ||||||
|  * none of the dynamic functionality. This page will be used to build up our corpus of |  * none of the dynamic functionality. This page will be used to build up our corpus of | ||||||
|  * permutations before start moving all message functionality to React. |  * permutations before we start moving all message functionality to React. | ||||||
|  */ |  */ | ||||||
| export class Message extends React.Component<{}, {}> { | export class Message extends React.Component<{}, {}> { | ||||||
|   public render() { |   public render() { | ||||||
|  | @ -13,12 +13,14 @@ export class Message extends React.Component<{}, {}> { | ||||||
|         <span className="avatar" /> |         <span className="avatar" /> | ||||||
|         <div className="bubble"> |         <div className="bubble"> | ||||||
|           <div className="sender" dir="auto" /> |           <div className="sender" dir="auto" /> | ||||||
|           <div className="attachments" /> |           <div className="inner-bubble"> | ||||||
|           <p className="content" dir="auto"> |             <div className="attachments" /> | ||||||
|             <span className="body"> |             <p className="content" dir="auto"> | ||||||
|               Hi there. How are you doing? Feeling pretty good? Awesome. |               <span className="body"> | ||||||
|             </span> |                 Hi there. How are you doing? Feeling pretty good? Awesome. | ||||||
|           </p> |               </span> | ||||||
|  |             </p> | ||||||
|  |            </div> | ||||||
|           <div className="meta"> |           <div className="meta"> | ||||||
|             <span |             <span | ||||||
|               className="timestamp" |               className="timestamp" | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Scott Nonnenberg
				Scott Nonnenberg