mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-11-16 14:49:00 +08:00
291 lines
7.6 KiB
JavaScript
291 lines
7.6 KiB
JavaScript
import {
|
|
IdentityStore,
|
|
FeatureUsageStore,
|
|
React,
|
|
PropTypes,
|
|
DOMUtils,
|
|
RegExpUtils,
|
|
Utils,
|
|
} from 'mailspring-exports';
|
|
import { RetinaImg } from 'mailspring-component-kit';
|
|
|
|
import ParticipantProfileDataSource from './participant-profile-data-source';
|
|
|
|
/* We expect ParticipantProfileDataSource.find to return the
|
|
* following schema:
|
|
* {
|
|
* profilePhotoUrl: string
|
|
* bio: string
|
|
* location: string
|
|
* currentTitle: string
|
|
* currentEmployer: string
|
|
* socialProfiles: hash keyed by type: ('twitter', 'facebook' etc)
|
|
* url: string
|
|
* handle: string
|
|
* }
|
|
*/
|
|
|
|
export default class SidebarParticipantProfile extends React.Component {
|
|
static displayName = 'SidebarParticipantProfile';
|
|
|
|
static propTypes = {
|
|
contact: PropTypes.object,
|
|
contactThreads: PropTypes.array,
|
|
};
|
|
|
|
static containerStyles = {
|
|
order: 0,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
loaded: false,
|
|
loading: false,
|
|
trialing: !IdentityStore.hasProFeatures(),
|
|
};
|
|
const contactState = ParticipantProfileDataSource.getCache(props.contact.email);
|
|
if (contactState) {
|
|
this.state = Object.assign(this.state, { loaded: true }, contactState);
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this._mounted = true;
|
|
if (!this.state.loaded && !this.state.trialing) {
|
|
// Wait until we know they've "settled" on this email to reduce the number of
|
|
// requests to the contact search endpoint.
|
|
this.setState({ loading: true });
|
|
setTimeout(this._onFindContact, 2000);
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this._mounted = false;
|
|
}
|
|
|
|
_onClickedToTry = async () => {
|
|
try {
|
|
await FeatureUsageStore.asyncUseFeature('contact-profiles', {
|
|
usedUpHeader: 'All Contact Previews Used',
|
|
usagePhrase: 'view contact profiles for',
|
|
iconUrl: 'mailspring://participant-profile/assets/ic-contact-profile-modal@2x.png',
|
|
});
|
|
} catch (err) {
|
|
// user does not have access to this feature
|
|
return;
|
|
}
|
|
this._onFindContact();
|
|
};
|
|
|
|
_onFindContact = async () => {
|
|
if (!this._mounted) {
|
|
return;
|
|
}
|
|
if (!this.state.loading) {
|
|
this.setState({ loading: true });
|
|
}
|
|
ParticipantProfileDataSource.find(this.props.contact.email).then(result => {
|
|
if (!this._mounted) {
|
|
return;
|
|
}
|
|
this.setState(Object.assign({ loading: false, loaded: true }, result));
|
|
});
|
|
};
|
|
|
|
_renderProfilePhoto() {
|
|
const hue = Utils.hueForString(this.props.contact.email);
|
|
const bgColor = `hsl(${hue}, 50%, 45%)`;
|
|
|
|
let content = (
|
|
<div className="default-profile-image" style={{ backgroundColor: bgColor }}>
|
|
{this.props.contact.nameAbbreviation()}
|
|
</div>
|
|
);
|
|
|
|
if (this.state.loading) {
|
|
content = (
|
|
<div className="default-profile-image">
|
|
<RetinaImg
|
|
className="spinner"
|
|
style={{ width: 20, height: 20 }}
|
|
name="inline-loading-spinner.gif"
|
|
mode={RetinaImg.Mode.ContentDark}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (this.state.profilePhotoUrl) {
|
|
content = <img alt="Profile" src={this.state.profilePhotoUrl} />;
|
|
}
|
|
|
|
return (
|
|
<div className="profile-photo-wrap">
|
|
<div className="profile-photo">{content}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_renderCorePersonalInfo() {
|
|
const fullName = this.props.contact.fullName();
|
|
let renderName = false;
|
|
if (fullName !== this.props.contact.email) {
|
|
renderName = (
|
|
<div className="selectable full-name" onClick={this._select}>
|
|
{this.props.contact.fullName()}
|
|
</div>
|
|
);
|
|
}
|
|
return (
|
|
<div className="core-personal-info">
|
|
{renderName}
|
|
<div className="selectable email" onClick={this._select}>
|
|
{this.props.contact.email}
|
|
</div>
|
|
{this._renderSocialProfiles()}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_renderSocialProfiles() {
|
|
if (!this.state.socialProfiles) {
|
|
return false;
|
|
}
|
|
const profiles = Object.entries(this.state.socialProfiles).map(([type, profile]) => {
|
|
return (
|
|
<a className="social-profile-item" key={type} title={profile.url} href={profile.url}>
|
|
<RetinaImg
|
|
url={`mailspring://participant-profile/assets/${type}-sidebar-icon@2x.png`}
|
|
mode={RetinaImg.Mode.ContentPreserve}
|
|
/>
|
|
</a>
|
|
);
|
|
});
|
|
return <div className="social-profiles-wrap">{profiles}</div>;
|
|
}
|
|
|
|
_renderAdditionalInfo() {
|
|
return (
|
|
<div className="additional-info">
|
|
{this._renderCurrentJob()}
|
|
{this._renderBio()}
|
|
{this._renderLocation()}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_renderCurrentJob() {
|
|
if (!this.state.employer) {
|
|
return false;
|
|
}
|
|
let title = false;
|
|
if (this.state.title) {
|
|
title = <span>{this.state.title}, </span>;
|
|
}
|
|
return (
|
|
<p className="selectable current-job">
|
|
{title}
|
|
{this.state.employer}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
_renderBio() {
|
|
if (!this.state.bio) {
|
|
return false;
|
|
}
|
|
|
|
const bioNodes = [];
|
|
const hashtagOrMentionRegex = RegExpUtils.hashtagOrMentionRegex();
|
|
|
|
let bioRemainder = this.state.bio;
|
|
let match = null;
|
|
let count = 0;
|
|
|
|
/* I thought we were friends. */
|
|
/* eslint no-cond-assign: 0 */
|
|
while ((match = hashtagOrMentionRegex.exec(bioRemainder))) {
|
|
// the first char of the match is whitespace, match[1] is # or @, match[2] is the tag itself.
|
|
bioNodes.push(bioRemainder.substr(0, match.index + 1));
|
|
if (match[1] === '#') {
|
|
bioNodes.push(
|
|
<a key={count} href={`https://twitter.com/hashtag/${match[2]}`}>{`#${match[2]}`}</a>
|
|
);
|
|
}
|
|
if (match[1] === '@') {
|
|
bioNodes.push(<a key={count} href={`https://twitter.com/${match[2]}`}>{`@${match[2]}`}</a>);
|
|
}
|
|
bioRemainder = bioRemainder.substr(match.index + match[0].length);
|
|
count += 1;
|
|
}
|
|
bioNodes.push(bioRemainder);
|
|
|
|
return <p className="selectable bio">{bioNodes}</p>;
|
|
}
|
|
|
|
_renderLocation() {
|
|
if (!this.state.location) {
|
|
return false;
|
|
}
|
|
return (
|
|
<p className="location">
|
|
<RetinaImg
|
|
url={`mailspring://participant-profile/assets/location-icon@2x.png`}
|
|
mode={RetinaImg.Mode.ContentPreserve}
|
|
style={{ float: 'left' }}
|
|
/>
|
|
<span className="selectable" style={{ display: 'block', marginLeft: 20 }}>
|
|
{this.state.location}
|
|
</span>
|
|
</p>
|
|
);
|
|
}
|
|
|
|
_select(event) {
|
|
const el = event.target;
|
|
const sel = document.getSelection();
|
|
if (el.contains(sel.anchorNode) && !sel.isCollapsed) {
|
|
return;
|
|
}
|
|
const anchor = DOMUtils.findFirstTextNode(el);
|
|
const focus = DOMUtils.findLastTextNode(el);
|
|
if (anchor && focus && focus.data) {
|
|
sel.setBaseAndExtent(anchor, 0, focus, focus.data.length);
|
|
}
|
|
}
|
|
|
|
_renderFindCTA() {
|
|
if (!this.state.trialing || this.state.loaded) {
|
|
return;
|
|
}
|
|
if (!this.props.contact.email || Utils.likelyNonHumanEmail(this.props.contact.email)) {
|
|
return;
|
|
}
|
|
|
|
return (
|
|
<div style={{ textAlign: 'center' }}>
|
|
<p>
|
|
The contact sidebar in Mailspring Pro shows information about the people and companies
|
|
you're emailing with.
|
|
</p>
|
|
<div className="btn" onClick={!this.state.loading ? this._onClickedToTry : null}>
|
|
{!this.state.loading ? `Try it Now` : `Loading...`}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div className="participant-profile">
|
|
{this._renderProfilePhoto()}
|
|
{this._renderCorePersonalInfo()}
|
|
{this._renderAdditionalInfo()}
|
|
|
|
{this._renderFindCTA()}
|
|
</div>
|
|
);
|
|
}
|
|
}
|