- {renderAvatar({ contact, i18n, module })}
+
+ {renderAvatar({ contact, i18n, size: 80 })}
+
{renderName({ contact, isIncoming, module })}
{renderContactShorthand({ contact, isIncoming, module })}
{this.renderSendMessage({ hasSignalAccount, i18n, onSendMessage })}
diff --git a/ts/components/conversation/ConversationHeader.tsx b/ts/components/conversation/ConversationHeader.tsx
index 0229db359..a81ded580 100644
--- a/ts/components/conversation/ConversationHeader.tsx
+++ b/ts/components/conversation/ConversationHeader.tsx
@@ -1,7 +1,7 @@
import React from 'react';
-import classNames from 'classnames';
import { Emojify } from './Emojify';
+import { Avatar } from '../Avatar';
import { Localizer } from '../../types/Util';
import {
ContextMenu,
@@ -45,10 +45,6 @@ interface Props {
onGoBack: () => void;
}
-function getInitial(name: string): string {
- return name.trim()[0] || '#';
-}
-
export class ConversationHeader extends React.Component
{
public captureMenuTriggerBound: (trigger: any) => void;
public showMenuBound: (event: React.MouseEvent) => void;
@@ -116,37 +112,25 @@ export class ConversationHeader extends React.Component {
avatarPath,
color,
i18n,
+ isGroup,
name,
phoneNumber,
profileName,
} = this.props;
- if (!avatarPath) {
- const initial = getInitial(name || '');
-
- return (
-
- {initial}
-
- );
- }
-
- const title = `${name || phoneNumber}${
- !name && profileName ? ` ~${profileName}` : ''
- }`;
-
return (
-
+
+
+
);
}
diff --git a/ts/components/conversation/EmbeddedContact.tsx b/ts/components/conversation/EmbeddedContact.tsx
index 607a2b554..b9a818d75 100644
--- a/ts/components/conversation/EmbeddedContact.tsx
+++ b/ts/components/conversation/EmbeddedContact.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames';
+import { Avatar } from '../Avatar';
import { Contact, getName } from '../../types/Contact';
import { Localizer } from '../../types/Util';
@@ -41,7 +42,7 @@ export class EmbeddedContact extends React.Component {
role="button"
onClick={onClick}
>
- {renderAvatar({ contact, i18n, module })}
+ {renderAvatar({ contact, i18n, size: 48 })}
{renderName({ contact, isIncoming, module })}
{renderContactShorthand({ contact, isIncoming, module })}
@@ -53,40 +54,29 @@ export class EmbeddedContact extends React.Component
{
// Note: putting these below the main component so style guide picks up EmbeddedContact
-function getInitial(name: string): string {
- return name.trim()[0] || '#';
-}
-
export function renderAvatar({
contact,
i18n,
- module,
+ size,
}: {
contact: Contact;
i18n: Localizer;
- module: string;
+ size: number;
}) {
const { avatar } = contact;
- const path = avatar && avatar.avatar && avatar.avatar.path;
+ const avatarPath = avatar && avatar.avatar && avatar.avatar.path;
const name = getName(contact) || '';
- if (!path) {
- const initials = getInitial(name);
-
- return (
-
- );
- }
-
return (
-
-

-
+
);
}
diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx
index 146826628..f9fb54019 100644
--- a/ts/components/conversation/Message.tsx
+++ b/ts/components/conversation/Message.tsx
@@ -6,6 +6,7 @@ import {
isVideoTypeSupported,
} from '../../util/GoogleChrome';
+import { Avatar } from '../Avatar';
import { MessageBody } from './MessageBody';
import { ExpireTimer, getIncrement } from './ExpireTimer';
import { Timestamp } from './Timestamp';
@@ -133,10 +134,6 @@ function canDisplayImage(attachment?: Attachment) {
return height > 0 && height <= 4096 && width > 0 && width <= 4096;
}
-function getInitial(name: string): string {
- return name.trim()[0] || '#';
-}
-
function getExtension({
fileName,
contentType,
@@ -633,21 +630,17 @@ export class Message extends React.Component {
public renderAvatar() {
const {
+ authorAvatarPath,
authorName,
authorPhoneNumber,
authorProfileName,
- authorAvatarPath,
- conversationColor,
collapseMetadata,
+ conversationColor,
conversationType,
direction,
i18n,
} = this.props;
- const title = `${authorName || authorPhoneNumber}${
- !authorName && authorProfileName ? ` ~${authorProfileName}` : ''
- }`;
-
if (
collapseMetadata ||
conversationType !== 'group' ||
@@ -656,26 +649,18 @@ export class Message extends React.Component {
return;
}
- if (!authorAvatarPath) {
- const label = authorName ? getInitial(authorName) : '#';
-
- return (
-
- );
- }
-
return (
-

+
);
}
diff --git a/ts/components/conversation/MessageDetail.tsx b/ts/components/conversation/MessageDetail.tsx
index fc20817f7..cb830fdea 100644
--- a/ts/components/conversation/MessageDetail.tsx
+++ b/ts/components/conversation/MessageDetail.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import classNames from 'classnames';
import moment from 'moment';
+import { Avatar } from '../Avatar';
import { ContactName } from './ContactName';
import { Message, Props as MessageProps } from './Message';
import { Localizer } from '../../types/Util';
@@ -31,40 +32,21 @@ interface Props {
i18n: Localizer;
}
-function getInitial(name: string): string {
- return name.trim()[0] || '#';
-}
-
export class MessageDetail extends React.Component {
public renderAvatar(contact: Contact) {
const { i18n } = this.props;
const { avatarPath, color, phoneNumber, name, profileName } = contact;
- if (!avatarPath) {
- const initial = getInitial(name || '');
-
- return (
-
- {initial}
-
- );
- }
-
- const title = `${name || phoneNumber}${
- !name && profileName ? ` ~${profileName}` : ''
- }`;
-
return (
-
);
}
diff --git a/ts/styleguide/LeftPaneContext.tsx b/ts/styleguide/LeftPaneContext.tsx
new file mode 100644
index 000000000..98a67ea17
--- /dev/null
+++ b/ts/styleguide/LeftPaneContext.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import classNames from 'classnames';
+
+interface Props {
+ /**
+ * Corresponds to the theme setting in the app, and the class added to the root element.
+ */
+ theme: 'light-theme' | 'dark-theme';
+}
+
+/**
+ * Provides the parent elements necessary to allow the main Signal Desktop stylesheet to
+ * apply (with no changes) to messages in the Style Guide.
+ */
+export class LeftPaneContext extends React.Component {
+ public render() {
+ const { theme } = this.props;
+
+ return (
+
+
{this.props.children}
+
+ );
+ }
+}
diff --git a/ts/styleguide/StyleGuideUtil.ts b/ts/styleguide/StyleGuideUtil.ts
index 0c4788881..a7679c0aa 100644
--- a/ts/styleguide/StyleGuideUtil.ts
+++ b/ts/styleguide/StyleGuideUtil.ts
@@ -6,6 +6,7 @@ import classNames from 'classnames';
import { default as _ } from 'lodash';
export { ConversationContext } from './ConversationContext';
+export { LeftPaneContext } from './LeftPaneContext';
export { _, classNames };
diff --git a/ts/util/getInitials.ts b/ts/util/getInitials.ts
new file mode 100644
index 000000000..b2c89ffc5
--- /dev/null
+++ b/ts/util/getInitials.ts
@@ -0,0 +1,21 @@
+const BAD_CHARACTERS = /[^A-Za-z\s]+/g;
+const WHITESPACE = /\s+/g;
+
+function removeNonInitials(name: string) {
+ return name.replace(BAD_CHARACTERS, '').replace(WHITESPACE, ' ');
+}
+
+export function getInitials(name?: string): string | null {
+ if (!name) {
+ return null;
+ }
+
+ const cleaned = removeNonInitials(name);
+ const parts = cleaned.split(' ');
+ const initials = parts.map(part => part.trim()[0]);
+ if (!initials.length) {
+ return null;
+ }
+
+ return initials.slice(0, 2).join('');
+}