Add company profile to the participant sidebar
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 597 B |
After Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 734 B After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
|
@ -2,8 +2,8 @@ import { MailspringAPIRequest, Utils } from 'mailspring-exports';
|
|||
const { makeRequest } = MailspringAPIRequest;
|
||||
|
||||
const CACHE_SIZE = 200;
|
||||
const CACHE_INDEX_KEY = 'pp-cache-keys';
|
||||
const CACHE_KEY_PREFIX = 'pp-cache-';
|
||||
const CACHE_INDEX_KEY = 'pp-cache-v3-keys';
|
||||
const CACHE_KEY_PREFIX = 'pp-cache-v3-';
|
||||
|
||||
class ParticipantProfileDataSource {
|
||||
constructor() {
|
||||
|
@ -37,56 +37,15 @@ class ParticipantProfileDataSource {
|
|||
return {};
|
||||
}
|
||||
|
||||
let person = (body || {}).person;
|
||||
|
||||
// This means there was no data about the person available. Return a
|
||||
// valid, but empty object for us to cache. This can happen when we
|
||||
// have company data, but no personal data.
|
||||
if (!person) {
|
||||
person = { email };
|
||||
if (!body.person) {
|
||||
body.person = { email };
|
||||
}
|
||||
if (!body.company) {
|
||||
body.company = {};
|
||||
}
|
||||
|
||||
const result = {
|
||||
cacheDate: Date.now(),
|
||||
email: email, // Used as checksum
|
||||
bio:
|
||||
person.bio ||
|
||||
(person.twitter && person.twitter.bio) ||
|
||||
(person.aboutme && person.aboutme.bio),
|
||||
location: person.location || (person.geo && person.geo.city) || null,
|
||||
currentTitle: person.employment && person.employment.title,
|
||||
currentEmployer: person.employment && person.employment.name,
|
||||
profilePhotoUrl: person.avatar,
|
||||
rawClearbitData: body,
|
||||
socialProfiles: this._socialProfiles(person),
|
||||
};
|
||||
|
||||
this.setCache(email, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
_socialProfiles(person = {}) {
|
||||
const profiles = {};
|
||||
|
||||
if (((person.twitter && person.twitter.handle) || '').length > 0) {
|
||||
profiles.twitter = {
|
||||
handle: person.twitter.handle,
|
||||
url: `https://twitter.com/${person.twitter.handle}`,
|
||||
};
|
||||
}
|
||||
if (((person.facebook && person.facebook.handle) || '').length > 0) {
|
||||
profiles.facebook = {
|
||||
handle: person.facebook.handle,
|
||||
url: `https://facebook.com/${person.facebook.handle}`,
|
||||
};
|
||||
}
|
||||
if (((person.linkedin && person.linkedin.handle) || '').length > 0) {
|
||||
profiles.linkedin = {
|
||||
handle: person.linkedin.handle,
|
||||
url: `https://linkedin.com/${person.linkedin.handle}`,
|
||||
};
|
||||
}
|
||||
return profiles;
|
||||
this.setCache(email, body);
|
||||
return body;
|
||||
}
|
||||
|
||||
// LocalStorage Retrieval / Saving
|
||||
|
|
|
@ -8,22 +8,171 @@ import {
|
|||
Utils,
|
||||
} from 'mailspring-exports';
|
||||
import { RetinaImg } from 'mailspring-component-kit';
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
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
|
||||
* }
|
||||
*/
|
||||
class ProfilePictureOrColorBox extends React.Component {
|
||||
static propTypes = {
|
||||
loading: PropTypes.bool,
|
||||
contact: PropTypes.object,
|
||||
profilePicture: PropTypes.string,
|
||||
};
|
||||
render() {
|
||||
const { contact, loading, avatar } = this.props;
|
||||
|
||||
const hue = Utils.hueForString(contact.email);
|
||||
const bgColor = `hsl(${hue}, 50%, 45%)`;
|
||||
|
||||
let content = (
|
||||
<div className="default-profile-image" style={{ backgroundColor: bgColor }}>
|
||||
{contact.nameAbbreviation()}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (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 (avatar) {
|
||||
content = <img alt="Profile" src={avatar} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="profile-photo-wrap">
|
||||
<div className="profile-photo">{content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
class SocialProfileLink extends React.Component {
|
||||
static propTypes = {
|
||||
service: PropTypes.string,
|
||||
handle: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { handle, service } = this.props;
|
||||
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<a
|
||||
className="social-profile-item"
|
||||
title={`https://${service}.com/${handle}`}
|
||||
href={`https://${service}.com/${handle}`}
|
||||
>
|
||||
<RetinaImg
|
||||
url={`mailspring://participant-profile/assets/${service}-sidebar-icon@2x.png`}
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TextBlockWithAutolinkedElements extends React.Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
string: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.props.string) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodes = [];
|
||||
const hashtagOrMentionRegex = RegExpUtils.hashtagOrMentionRegex();
|
||||
|
||||
let remainder = this.props.string;
|
||||
let match = null;
|
||||
let count = 0;
|
||||
|
||||
/* I thought we were friends. */
|
||||
/* eslint no-cond-assign: 0 */
|
||||
while ((match = hashtagOrMentionRegex.exec(remainder))) {
|
||||
// the first char of the match is whitespace, match[1] is # or @, match[2] is the tag itself.
|
||||
nodes.push(remainder.substr(0, match.index + 1));
|
||||
if (match[1] === '#') {
|
||||
nodes.push(
|
||||
<a key={count} href={`https://twitter.com/hashtag/${match[2]}`}>{`#${match[2]}`}</a>
|
||||
);
|
||||
}
|
||||
if (match[1] === '@') {
|
||||
nodes.push(<a key={count} href={`https://twitter.com/${match[2]}`}>{`@${match[2]}`}</a>);
|
||||
}
|
||||
remainder = remainder.substr(match.index + match[0].length);
|
||||
count += 1;
|
||||
}
|
||||
nodes.push(remainder);
|
||||
|
||||
return <p className={`selectable ${this.props.className}`}>{nodes}</p>;
|
||||
}
|
||||
}
|
||||
|
||||
class IconRow extends React.Component {
|
||||
static propTypes = {
|
||||
string: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { string, icon } = this.props;
|
||||
|
||||
if (!string) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<div className={`icon-row ${icon}`}>
|
||||
<RetinaImg
|
||||
url={`mailspring://participant-profile/assets/${icon}-icon@2x.png`}
|
||||
mode={RetinaImg.Mode.ContentPreserve}
|
||||
style={{ float: 'left' }}
|
||||
/>
|
||||
<span className="selectable" style={{ display: 'block', marginLeft: 25 }}>
|
||||
{string}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LocationRow extends React.Component {
|
||||
static propTypes = {
|
||||
string: PropTypes.string,
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<IconRow
|
||||
icon="location"
|
||||
string={
|
||||
this.props.string && (
|
||||
<span>
|
||||
{this.props.string}
|
||||
{' ['}
|
||||
<a className="plain" href={`https://maps.google.com/?q=${this.props.string}`}>
|
||||
View
|
||||
</a>
|
||||
{']'}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class SidebarParticipantProfile extends React.Component {
|
||||
static displayName = 'SidebarParticipantProfile';
|
||||
|
@ -94,156 +243,7 @@ export default class SidebarParticipantProfile extends React.Component {
|
|||
});
|
||||
};
|
||||
|
||||
_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) {
|
||||
_onSelect = event => {
|
||||
const el = event.target;
|
||||
const sel = document.getSelection();
|
||||
if (el.contains(sel.anchorNode) && !sel.isCollapsed) {
|
||||
|
@ -254,7 +254,7 @@ export default class SidebarParticipantProfile extends React.Component {
|
|||
if (anchor && focus && focus.data) {
|
||||
sel.setBaseAndExtent(anchor, 0, focus, focus.data.length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_renderFindCTA() {
|
||||
if (!this.state.trialing || this.state.loaded) {
|
||||
|
@ -277,12 +277,150 @@ export default class SidebarParticipantProfile extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
_renderCompanyInfo() {
|
||||
const {
|
||||
name,
|
||||
domain,
|
||||
category,
|
||||
description,
|
||||
location,
|
||||
timeZone,
|
||||
logo,
|
||||
facebook,
|
||||
twitter,
|
||||
linkedin,
|
||||
crunchbase,
|
||||
type,
|
||||
ticker,
|
||||
phone,
|
||||
metrics,
|
||||
} =
|
||||
this.state.company || {};
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let employees = null;
|
||||
let funding = null;
|
||||
|
||||
if (metrics) {
|
||||
if (metrics.raised) {
|
||||
funding = `Raised $${(metrics.raised / 1 || 0).toLocaleString()}`;
|
||||
} else if (metrics.marketCap) {
|
||||
funding = `Market cap $${(metrics.marketCap / 1 || 0).toLocaleString()}`;
|
||||
}
|
||||
|
||||
if (metrics.employees) {
|
||||
employees = `${(metrics.employees / 1 || 0).toLocaleString()} employees`;
|
||||
} else if (metrics.employeesRange) {
|
||||
employees = `${metrics.employeesRange} employees`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="company-profile">
|
||||
{logo && (
|
||||
<RetinaImg url={logo} className="company-logo" mode={RetinaImg.Mode.ContentPreserve} />
|
||||
)}
|
||||
|
||||
<div className="selectable larger" onClick={this._onSelect}>
|
||||
{name}
|
||||
</div>
|
||||
|
||||
{domain && (
|
||||
<a className="domain" href={domain.startsWith('http') ? domain : `http://${domain}`}>
|
||||
{domain}
|
||||
</a>
|
||||
)}
|
||||
|
||||
<div className="additional-info">
|
||||
<TextBlockWithAutolinkedElements string={description} className="description" />
|
||||
<LocationRow string={location} />
|
||||
<IconRow
|
||||
icon="timezone"
|
||||
string={
|
||||
timeZone && (
|
||||
<span>
|
||||
{`${timeZone.replace('_', ' ')} - `}
|
||||
<strong>
|
||||
{`Currently ${moment()
|
||||
.tz(timeZone)
|
||||
.format('h:MMa')}`}
|
||||
</strong>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IconRow icon="industry" string={category && (category.industry || category.sector)} />
|
||||
<IconRow
|
||||
icon="holding"
|
||||
string={{ private: 'Privately Held', public: `Stock Symbol ${ticker}` }[type]}
|
||||
/>
|
||||
<IconRow icon="phone" string={phone} />
|
||||
<IconRow icon="employees" string={employees} />
|
||||
<IconRow icon="funding" string={funding} />
|
||||
|
||||
<div className="social-profiles-wrap">
|
||||
<SocialProfileLink service="facebook" handle={facebook && facebook.handle} />
|
||||
<SocialProfileLink service="crunchbase" handle={crunchbase && crunchbase.handle} />
|
||||
<SocialProfileLink service="linkedin" handle={linkedin && linkedin.handle} />
|
||||
<SocialProfileLink service="twitter" handle={twitter && twitter.handle} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderPersonInfo() {
|
||||
const { facebook, linkedin, twitter, employment, location, bio } = this.state.person || {};
|
||||
|
||||
return (
|
||||
<div className="participant-profile">
|
||||
{this._renderProfilePhoto()}
|
||||
{this._renderCorePersonalInfo()}
|
||||
{this._renderAdditionalInfo()}
|
||||
<ProfilePictureOrColorBox
|
||||
loading={this.state.loading}
|
||||
avatar={this.state.avatar}
|
||||
contact={this.props.contact}
|
||||
/>
|
||||
<div className="personal-info">
|
||||
{this.props.contact.fullName() !== this.props.contact.email && (
|
||||
<div className="selectable larger" onClick={this._onSelect}>
|
||||
{this.props.contact.fullName()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{employment && (
|
||||
<div className="selectable current-job">
|
||||
{employment.title && <span>{employment.title}, </span>}
|
||||
{employment.name}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="selectable email" onClick={this._onSelect}>
|
||||
{this.props.contact.email}
|
||||
</div>
|
||||
|
||||
<div className="social-profiles-wrap">
|
||||
<SocialProfileLink service="facebook" handle={facebook && facebook.handle} />
|
||||
<SocialProfileLink service="linkedin" handle={linkedin && linkedin.handle} />
|
||||
<SocialProfileLink service="twitter" handle={twitter && twitter.handle} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="additional-info">
|
||||
<TextBlockWithAutolinkedElements string={bio} className="bio" />
|
||||
<LocationRow string={location} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this._renderPersonInfo()}
|
||||
|
||||
{this._renderCompanyInfo()}
|
||||
|
||||
{this._renderFindCTA()}
|
||||
</div>
|
||||
|
|
|
@ -45,9 +45,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
.participant-profile {
|
||||
.company-profile {
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid @border-color-divider;
|
||||
.company-logo {
|
||||
margin-left: 12px;
|
||||
float: right;
|
||||
max-width: 60px;
|
||||
max-height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.participant-profile,
|
||||
.company-profile {
|
||||
margin-bottom: 22px;
|
||||
|
||||
.larger {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.profile-photo-wrap {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
@ -87,7 +103,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.core-personal-info {
|
||||
.social-profiles-wrap {
|
||||
margin-bottom: @spacing-standard;
|
||||
}
|
||||
.social-profile-item {
|
||||
margin: 0 10px;
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
padding-top: 30px;
|
||||
text-align: center;
|
||||
margin-bottom: @spacing-standard;
|
||||
|
@ -99,24 +128,24 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.full-name {
|
||||
font-size: 16px;
|
||||
}
|
||||
.email {
|
||||
color: @text-color-very-subtle;
|
||||
margin-bottom: @spacing-standard;
|
||||
}
|
||||
.social-profiles-wrap {
|
||||
margin-bottom: @spacing-standard;
|
||||
}
|
||||
.social-profile-item {
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.additional-info {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
clear: both;
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.icon-row {
|
||||
margin-bottom: 8px;
|
||||
a.plain {
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
.bio {
|
||||
color: @text-color-very-subtle;
|
||||
|
@ -125,7 +154,8 @@
|
|||
}
|
||||
|
||||
body.platform-win32 {
|
||||
.participant-profile {
|
||||
.participant-profile,
|
||||
.company-profile {
|
||||
border-radius: 0;
|
||||
.profile-photo {
|
||||
border-radius: 0;
|
||||
|
|
|
@ -81,7 +81,9 @@ class DraftChangeSet extends EventEmitter {
|
|||
|
||||
this._saving = this._pending;
|
||||
this._pending = {};
|
||||
console.log('_saving = ' + JSON.stringify(this._saving));
|
||||
return this.callbacks.onCommit().then(() => {
|
||||
console.log('_saving cleared');
|
||||
this._saving = {};
|
||||
});
|
||||
};
|
||||
|
@ -347,6 +349,8 @@ export default class DraftEditingSession extends MailspringStore {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('_onDraftChanged');
|
||||
|
||||
// If our draft has been changed, only accept values which are present.
|
||||
// If `body` is undefined, assume it's not loaded. Do not overwrite old body.
|
||||
const nextDraft = change.objects
|
||||
|
@ -364,6 +368,7 @@ export default class DraftEditingSession extends MailspringStore {
|
|||
}
|
||||
nextValues[key] = nextDraft[key];
|
||||
}
|
||||
console.log('_setDraft nextValues: ' + JSON.stringify(nextValues));
|
||||
this._setDraft(Object.assign(new Message(), this._draft, nextValues));
|
||||
this.trigger();
|
||||
}
|
||||
|
@ -391,8 +396,12 @@ export default class DraftEditingSession extends MailspringStore {
|
|||
const baseDraft = draft || inMemoryDraft;
|
||||
const updatedDraft = this.changes.applyToModel(baseDraft);
|
||||
const task = new SyncbackDraftTask({ draft: updatedDraft });
|
||||
console.log('changeSetCommit queueing task');
|
||||
Actions.queueTask(task);
|
||||
await TaskQueue.waitForPerformLocal(task);
|
||||
console.log(
|
||||
'changeSetCommit finished waiting for performLocal. At this point, onDraftChanged should have been called.'
|
||||
);
|
||||
}
|
||||
|
||||
// Undo / Redo
|
||||
|
|
|
@ -355,8 +355,12 @@ class DraftStore extends MailspringStore {
|
|||
// completely saved and the user won't see old content briefly.
|
||||
const session = await this.sessionForClientId(headerMessageId);
|
||||
await session.ensureCorrectAccount();
|
||||
await session.changes.commit();
|
||||
let draft = session.draft();
|
||||
console.log('1:');
|
||||
console.log(JSON.stringify(draft));
|
||||
await session.changes.commit();
|
||||
console.log('2:');
|
||||
console.log(JSON.stringify(session.draft()));
|
||||
await session.teardown();
|
||||
|
||||
draft = await DraftHelpers.applyExtensionTransforms(draft);
|
||||
|
@ -369,6 +373,9 @@ class DraftStore extends MailspringStore {
|
|||
// the new message text (and never old draft text or blank text) sending.
|
||||
await MessageBodyProcessor.updateCacheForMessage(draft);
|
||||
|
||||
console.log('3:');
|
||||
console.log(JSON.stringify(draft));
|
||||
|
||||
// At this point the message UI enters the sending state and the composer is unmounted.
|
||||
this.trigger({ headerMessageId });
|
||||
|
||||
|
|