Full support for quotations in Android theme
This commit is contained in:
parent
47a3acd5c9
commit
1cc0633786
13 changed files with 734 additions and 128 deletions
|
@ -13,7 +13,7 @@ export class Message extends React.Component<{}, {}> {
|
|||
<span className="avatar" />
|
||||
<div className="bubble">
|
||||
<div className="sender" dir="auto" />
|
||||
<div className="inner-bubble">
|
||||
<div className="inner-bubble with-tail">
|
||||
<div className="attachments" />
|
||||
<p className="content" dir="auto">
|
||||
<span className="body">
|
||||
|
|
|
@ -10,15 +10,85 @@ const outgoing = new Whisper.Message({
|
|||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
text: 'How many ferrets do you have?',
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
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>
|
||||
```
|
||||
|
||||
#### In a group conversation
|
||||
|
||||
```jsx
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: 'About six',
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
text: 'How many ferrets do you have?',
|
||||
author: '+12025550010',
|
||||
id: Date.now() - 1000,
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550007',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550002',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
<util.ConversationContext theme={util.theme} conversationType="group">
|
||||
<util.BackboneWrapper
|
||||
View={View}
|
||||
options={{ model: incoming }}
|
||||
/>
|
||||
<util.BackboneWrapper
|
||||
View={View}
|
||||
options={{ model: outgoing }}
|
||||
/>
|
||||
</util.ConversationContext>
|
||||
```
|
||||
|
||||
#### A lot of text in quotation
|
||||
|
||||
```jsx
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: 'About six',
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
text:
|
||||
'I have lots of things to say. First, I enjoy otters. Second best are cats. ' +
|
||||
'After that, probably dogs. And then, you know, reptiles of all types. ' +
|
||||
'Then birds. They are dinosaurs, after all. Then cephalapods, because they are ' +
|
||||
'really smart.',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
@ -37,13 +107,17 @@ const View = Whisper.MessageView;
|
|||
#### Image with caption
|
||||
|
||||
```jsx
|
||||
const quotedMessage = {
|
||||
imageUrl: util.gifObjectUrl,
|
||||
id: '3234-23423-2342',
|
||||
};
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Totally, it's a pretty unintuitive concept.",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
text: 'I am pretty confused about Pi.',
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -51,19 +125,22 @@ const outgoing = new Whisper.Message({
|
|||
fileName: 'pi.gif',
|
||||
thumbnail: {
|
||||
contentType: 'image/gif',
|
||||
data: util.gif,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
outgoing.quotedMessage = quotedMessage;
|
||||
incoming.quotedMessage = quotedMessage;
|
||||
|
||||
const View = Whisper.MessageView;
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<util.BackboneWrapper
|
||||
|
@ -80,12 +157,16 @@ const View = Whisper.MessageView;
|
|||
#### Image
|
||||
|
||||
```jsx
|
||||
const quotedMessage = {
|
||||
imageUrl: util.gifObjectUrl,
|
||||
};
|
||||
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Yeah, pi. Tough to wrap your head around.",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -93,19 +174,61 @@ const outgoing = new Whisper.Message({
|
|||
fileName: 'pi.gif',
|
||||
thumbnail: {
|
||||
contentType: 'image/gif',
|
||||
data: util.gif,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
outgoing.quotedMessage = quotedMessage;
|
||||
incoming.quotedMessage = quotedMessage;
|
||||
|
||||
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>
|
||||
```
|
||||
|
||||
#### Image with no thumbnail
|
||||
|
||||
```jsx
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Yeah, pi. Tough to wrap your head around.",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'image/gif',
|
||||
fileName: 'pi.gif',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
const View = Whisper.MessageView;
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<util.BackboneWrapper
|
||||
|
@ -122,12 +245,16 @@ const View = Whisper.MessageView;
|
|||
#### Video with caption
|
||||
|
||||
```jsx
|
||||
const quotedMessage = {
|
||||
imageUrl: util.gifObjectUrl,
|
||||
};
|
||||
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Sweet the way the video sneaks up on you!",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
text: 'Check out this video I found!',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
|
@ -136,19 +263,22 @@ const outgoing = new Whisper.Message({
|
|||
fileName: 'freezing_bubble.mp4',
|
||||
thumbnail: {
|
||||
contentType: 'image/gif',
|
||||
data: util.gif,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
outgoing.quotedMessage = quotedMessage;
|
||||
incoming.quotedMessage = quotedMessage;
|
||||
|
||||
const View = Whisper.MessageView;
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<util.BackboneWrapper
|
||||
|
@ -165,12 +295,16 @@ const View = Whisper.MessageView;
|
|||
#### Video
|
||||
|
||||
```jsx
|
||||
const quotedMessage = {
|
||||
imageUrl: util.gifObjectUrl,
|
||||
};
|
||||
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Awesome!",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -185,12 +319,55 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
outgoing.quotedMessage = quotedMessage;
|
||||
incoming.quotedMessage = quotedMessage;
|
||||
|
||||
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 with no thumbnail
|
||||
|
||||
```jsx
|
||||
const outgoing = new Whisper.Message({
|
||||
type: 'outgoing',
|
||||
body: "Awesome!",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
contentType: 'video/mp4',
|
||||
fileName: 'freezing_bubble.mp4',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
|
||||
const View = Whisper.MessageView;
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<util.BackboneWrapper
|
||||
|
@ -212,7 +389,7 @@ const outgoing = new Whisper.Message({
|
|||
body: 'I really like it!',
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
text: 'Check out this beautiful song!',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
|
@ -224,10 +401,10 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
@ -251,7 +428,7 @@ const outgoing = new Whisper.Message({
|
|||
body: 'I really like it!',
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -262,10 +439,10 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
@ -289,7 +466,7 @@ const outgoing = new Whisper.Message({
|
|||
body: 'I really like it!',
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -302,10 +479,10 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
@ -329,7 +506,7 @@ const outgoing = new Whisper.Message({
|
|||
body: "I can't read latin.",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
text: 'This is my manifesto. Tell me what you think!',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
|
@ -341,10 +518,10 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
@ -368,7 +545,7 @@ const outgoing = new Whisper.Message({
|
|||
body: "Sorry, I can't read latin!",
|
||||
sent_at: Date.now() - 18000000,
|
||||
quote: {
|
||||
author: '+12025550100',
|
||||
author: '+12025550011',
|
||||
id: Date.now() - 1000,
|
||||
attachments: [
|
||||
{
|
||||
|
@ -379,10 +556,10 @@ const outgoing = new Whisper.Message({
|
|||
},
|
||||
});
|
||||
const incoming = new Whisper.Message(Object.assign({}, outgoing.attributes, {
|
||||
source: '+12025550100',
|
||||
source: '+12025550011',
|
||||
type: 'incoming',
|
||||
quote: Object.assign({}, outgoing.attributes.quote, {
|
||||
author: '+12025550200',
|
||||
author: '+12025550005',
|
||||
}),
|
||||
}));
|
||||
const View = Whisper.MessageView;
|
||||
|
|
|
@ -7,21 +7,28 @@ import Mime from '../../../js/modules/types/mime';
|
|||
|
||||
interface Props {
|
||||
i18n: (key: string, values?: Array<string>) => string;
|
||||
authorName: string;
|
||||
authorTitle: string;
|
||||
authorProfileName?: string;
|
||||
authorColor: string;
|
||||
attachments: Array<QuotedAttachment>;
|
||||
text: string;
|
||||
attachments: Array<QuotedAttachment>;
|
||||
openQuotedMessage?: () => void;
|
||||
quoterAuthorColor?: string,
|
||||
isIncoming: boolean,
|
||||
}
|
||||
|
||||
interface QuotedAttachment {
|
||||
fileName: string;
|
||||
contentType: string;
|
||||
thumbnail?: Attachment,
|
||||
/* Not included in protobuf */
|
||||
isVoiceMessage: boolean;
|
||||
objectUrl: string;
|
||||
thumbnail: {
|
||||
contentType: string;
|
||||
data: ArrayBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
interface Attachment {
|
||||
contentType: string;
|
||||
/* Not included in protobuf, and is loaded asynchronously */
|
||||
objectUrl?: string;
|
||||
}
|
||||
|
||||
function validateQuote(quote: Props): boolean {
|
||||
|
@ -36,51 +43,68 @@ function validateQuote(quote: Props): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getContentType(attachments: Array<QuotedAttachment>): string | null {
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
function getObjectUrl(thumbnail: Attachment | undefined): string | null {
|
||||
if (thumbnail && thumbnail.objectUrl) {
|
||||
return thumbnail.objectUrl;
|
||||
}
|
||||
|
||||
const first = attachments[0];
|
||||
return first.contentType;
|
||||
return null;
|
||||
}
|
||||
|
||||
export class Quote extends React.Component<Props, {}> {
|
||||
public renderIcon(first: QuotedAttachment) {
|
||||
const contentType = first.contentType;
|
||||
const objectUrl = first.objectUrl;
|
||||
public renderImage(url: string, icon?: string) {
|
||||
return (
|
||||
<div className="icon-container">
|
||||
<div className="inner">
|
||||
<img src={url} />
|
||||
{icon
|
||||
? <div className={classnames('icon', icon)}></div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 renderIcon(icon: string) {
|
||||
const { authorColor, isIncoming, quoterAuthorColor } = this.props;
|
||||
|
||||
const backgroundColor = isIncoming ? 'white' : authorColor;
|
||||
const iconColor = isIncoming ? quoterAuthorColor : 'white';
|
||||
|
||||
return (
|
||||
<div className='icon-container'>
|
||||
<div className={classnames('circle-background', backgroundColor)}></div>
|
||||
<div className={classnames('icon', icon, iconColor)}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public renderIconContainer() {
|
||||
const { attachments } = this.props;
|
||||
|
||||
if (!attachments || attachments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const first = attachments[0];
|
||||
const { contentType, thumbnail } = first;
|
||||
const objectUrl = getObjectUrl(thumbnail);
|
||||
|
||||
return <div className='icon-container'>
|
||||
{this.renderIcon(first)}
|
||||
</div>
|
||||
if (Mime.isVideo(contentType)) {
|
||||
return objectUrl
|
||||
? this.renderImage(objectUrl, 'play')
|
||||
: this.renderIcon('play');
|
||||
}
|
||||
if (Mime.isImage(contentType)) {
|
||||
return objectUrl
|
||||
? this.renderImage(objectUrl)
|
||||
: this.renderIcon('image');
|
||||
}
|
||||
if (Mime.isAudio(contentType)) {
|
||||
return this.renderIcon('microphone');
|
||||
}
|
||||
|
||||
return this.renderIcon('file');
|
||||
}
|
||||
|
||||
public renderText() {
|
||||
|
@ -94,20 +118,19 @@ export class Quote extends React.Component<Props, {}> {
|
|||
return null;
|
||||
}
|
||||
|
||||
const contentType = getContentType(attachments);
|
||||
const first = attachments[0];
|
||||
const fileName = first.fileName;
|
||||
|
||||
console.log(contentType);
|
||||
const { contentType, fileName, isVoiceMessage } = first;
|
||||
|
||||
if (Mime.isVideo(contentType)) {
|
||||
return <div className='type-label'>{i18n('video')}</div>;
|
||||
} else if (Mime.isImage(contentType)) {
|
||||
}
|
||||
if (Mime.isImage(contentType)) {
|
||||
return <div className='type-label'>{i18n('photo')}</div>;
|
||||
} else if (Mime.isAudio(contentType) && first.isVoiceMessage) {
|
||||
}
|
||||
if (Mime.isAudio(contentType) && isVoiceMessage) {
|
||||
return <div className='type-label'>{i18n('voiceMessage')}</div>;
|
||||
} else if (Mime.isAudio(contentType)) {
|
||||
console.log(first);
|
||||
}
|
||||
if (Mime.isAudio(contentType)) {
|
||||
return <div className='type-label'>{i18n('audio')}</div>;
|
||||
}
|
||||
|
||||
|
@ -115,16 +138,27 @@ export class Quote extends React.Component<Props, {}> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { authorName, authorColor } = this.props;
|
||||
const {
|
||||
authorTitle,
|
||||
authorProfileName,
|
||||
authorColor,
|
||||
openQuotedMessage,
|
||||
} = this.props;
|
||||
|
||||
if (!validateQuote(this.props)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames(authorColor, 'quote')} >
|
||||
<div onClick={openQuotedMessage} className={classnames(authorColor, 'quote')} >
|
||||
<div className="primary">
|
||||
<div className="author">{authorName}</div>
|
||||
<div className={classnames(authorColor, 'author')}>
|
||||
{authorTitle}{' '}
|
||||
{authorProfileName
|
||||
? <span className='profile-name'>~{authorProfileName}</span>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
{this.renderText()}
|
||||
</div>
|
||||
{this.renderIconContainer()}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue