From 300d5dbc7f811a384811a9877f09d18182242027 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 11:43:51 -0800 Subject: [PATCH 01/30] fix(sidebar): Prevent error in snooze item when category doesnt exist - Add Snooze category to locked and hidden categories to prevent from directly moving stuff to it and showing up in the user categories section --- internal_packages/account-sidebar/lib/sidebar-item.coffee | 3 +++ src/flux/models/category.coffee | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal_packages/account-sidebar/lib/sidebar-item.coffee b/internal_packages/account-sidebar/lib/sidebar-item.coffee index da5e93f64..feec534ab 100644 --- a/internal_packages/account-sidebar/lib/sidebar-item.coffee +++ b/internal_packages/account-sidebar/lib/sidebar-item.coffee @@ -115,10 +115,13 @@ class SidebarItem id += "-#{opts.name}" if opts.name opts.name = "Snoozed" unless opts.name opts.iconName= 'snooze.png' + categories = accountIds.map (accId) => _.findWhere CategoryStore.userCategories(accId), {displayName} + categories = _.compact(categories) perspective = MailboxPerspective.forCategories(categories) + perspective.name = id unless perspective.name @forPerspective(id, perspective, opts) @forStarred: (accountIds, opts = {}) -> diff --git a/src/flux/models/category.coffee b/src/flux/models/category.coffee index eba041584..a85c818a4 100644 --- a/src/flux/models/category.coffee +++ b/src/flux/models/category.coffee @@ -18,6 +18,7 @@ StandardCategories = { LockedCategories = { "sent" + "N1-Snoozed" } HiddenCategories = { @@ -27,6 +28,7 @@ HiddenCategories = { "archive" "starred" "important" + "N1-Snoozed" } AllMailName = "all" @@ -110,10 +112,10 @@ class Category extends Model StandardCategories[@name]? and @name isnt 'important' isLockedCategory: -> - LockedCategories[@name]? + LockedCategories[@name]? or LockedCategories[@displayName]? isHiddenCategory: -> - HiddenCategories[@name]? + HiddenCategories[@name]? or HiddenCategories[@displayName]? isUserCategory: -> not @isStandardCategory() and not @isHiddenCategory() From e02b924efb9e18d08f2152a9604f26b59d38083d Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 11:50:43 -0800 Subject: [PATCH 02/30] :lipstick: (send-later): Update placeholder copy, again --- internal_packages/send-later/lib/send-later-popover.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal_packages/send-later/lib/send-later-popover.jsx b/internal_packages/send-later/lib/send-later-popover.jsx index 83617e42a..4aae2e975 100644 --- a/internal_packages/send-later/lib/send-later-popover.jsx +++ b/internal_packages/send-later/lib/send-later-popover.jsx @@ -81,7 +81,7 @@ class SendLaterPopover extends Component { updateInputDateValue(event.target.value)}/> {dateInterpretation} From 0550b092b74e84e8d30619edf928d89a86d0c5f7 Mon Sep 17 00:00:00 2001 From: Drew Regitsky Date: Wed, 24 Feb 2016 12:00:40 -0800 Subject: [PATCH 03/30] fix(plugins): fix open tracking and link tracking URLs --- .../link-tracking/lib/link-tracking-composer-extension.es6 | 2 +- internal_packages/link-tracking/lib/link-tracking-constants.es6 | 2 +- internal_packages/link-tracking/lib/main.es6 | 2 +- internal_packages/open-tracking/lib/main.es6 | 2 +- .../open-tracking/lib/open-tracking-composer-extension.es6 | 2 +- internal_packages/open-tracking/lib/open-tracking-constants.es6 | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 index 687782a67..62dee0e87 100644 --- a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 @@ -29,7 +29,7 @@ export default class LinkTrackingComposerExtension extends ComposerExtension { // loop through all elements, replace with redirect links and save mappings draftBody.unquoted = draftBody.unquoted.replace(RegExpUtils.linkTagRegex(), (match, prefix, url, suffix, content, closingTag) => { const encoded = encodeURIComponent(url); - const redirectUrl = `http://${PLUGIN_URL}/${draft.accountId}/${messageUid}/${links.length}?redirect=${encoded}`; + const redirectUrl = `http://${PLUGIN_URL}/link/${draft.accountId}/${messageUid}/${links.length}?redirect=${encoded}`; links.push({url: url, click_count: 0, click_data: [], redirect_url: redirectUrl}); return prefix + redirectUrl + suffix + content + closingTag; }); diff --git a/internal_packages/link-tracking/lib/link-tracking-constants.es6 b/internal_packages/link-tracking/lib/link-tracking-constants.es6 index a1d78b44e..8053c616b 100644 --- a/internal_packages/link-tracking/lib/link-tracking-constants.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-constants.es6 @@ -2,4 +2,4 @@ import plugin from '../package.json' export const PLUGIN_NAME = plugin.title export const PLUGIN_ID = plugin.appId[NylasEnv.config.get("env")]; -export const PLUGIN_URL = "https://edgehill-staging.nylas.com/plugins"; +export const PLUGIN_URL = "https://edgehill-staging.nylas.com"; diff --git a/internal_packages/link-tracking/lib/main.es6 b/internal_packages/link-tracking/lib/main.es6 index 81298bb3f..fb3007459 100644 --- a/internal_packages/link-tracking/lib/main.es6 +++ b/internal_packages/link-tracking/lib/main.es6 @@ -25,7 +25,7 @@ function afterDraftSend({draftClientId}) { // post the uid and message id pair to the plugin server const data = {uid: uid, message_id: message.id}; - const serverUrl = `${PLUGIN_URL}/register-message`; + const serverUrl = `${PLUGIN_URL}/plugins/register-message`; return post({ url: serverUrl, body: JSON.stringify(data), diff --git a/internal_packages/open-tracking/lib/main.es6 b/internal_packages/open-tracking/lib/main.es6 index 569400632..0a3fcd418 100644 --- a/internal_packages/open-tracking/lib/main.es6 +++ b/internal_packages/open-tracking/lib/main.es6 @@ -27,7 +27,7 @@ function afterDraftSend({draftClientId}) { // post the uid and message id pair to the plugin server const data = {uid: uid, message_id: message.id, thread_id: 1}; - const serverUrl = `${PLUGIN_URL}/register-message`; + const serverUrl = `${PLUGIN_URL}/plugins/register-message`; return post({ url: serverUrl, body: JSON.stringify(data), diff --git a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 index c25f5f5e6..803a5692f 100644 --- a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 +++ b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 @@ -21,7 +21,7 @@ export default class OpenTrackingComposerExtension extends ComposerExtension { const uid = uuid.v4().replace(/-/g, ""); // insert a tracking pixel into the message - const serverUrl = `http://${PLUGIN_URL}/${draft.accountId}/${uid}`; + const serverUrl = `http://${PLUGIN_URL}/open/${draft.accountId}/${uid}`; const img = ``; const draftBody = new DraftBody(draft); draftBody.unquoted = draftBody.unquoted + "
" + img; diff --git a/internal_packages/open-tracking/lib/open-tracking-constants.es6 b/internal_packages/open-tracking/lib/open-tracking-constants.es6 index a1d78b44e..8053c616b 100644 --- a/internal_packages/open-tracking/lib/open-tracking-constants.es6 +++ b/internal_packages/open-tracking/lib/open-tracking-constants.es6 @@ -2,4 +2,4 @@ import plugin from '../package.json' export const PLUGIN_NAME = plugin.title export const PLUGIN_ID = plugin.appId[NylasEnv.config.get("env")]; -export const PLUGIN_URL = "https://edgehill-staging.nylas.com/plugins"; +export const PLUGIN_URL = "https://edgehill-staging.nylas.com"; From f88445af15e9287d912812ff4fe61eee07840f50 Mon Sep 17 00:00:00 2001 From: Drew Regitsky Date: Wed, 24 Feb 2016 12:17:39 -0800 Subject: [PATCH 04/30] fix(plugins): more fixes to open/link tracking urls --- .../link-tracking/lib/link-tracking-composer-extension.es6 | 2 +- .../open-tracking/lib/open-tracking-composer-extension.es6 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 index 62dee0e87..8695109a5 100644 --- a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 @@ -29,7 +29,7 @@ export default class LinkTrackingComposerExtension extends ComposerExtension { // loop through all
elements, replace with redirect links and save mappings draftBody.unquoted = draftBody.unquoted.replace(RegExpUtils.linkTagRegex(), (match, prefix, url, suffix, content, closingTag) => { const encoded = encodeURIComponent(url); - const redirectUrl = `http://${PLUGIN_URL}/link/${draft.accountId}/${messageUid}/${links.length}?redirect=${encoded}`; + const redirectUrl = `${PLUGIN_URL}/link/${draft.accountId}/${messageUid}/${links.length}?redirect=${encoded}`; links.push({url: url, click_count: 0, click_data: [], redirect_url: redirectUrl}); return prefix + redirectUrl + suffix + content + closingTag; }); diff --git a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 index 803a5692f..d99ca9269 100644 --- a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 +++ b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 @@ -21,7 +21,7 @@ export default class OpenTrackingComposerExtension extends ComposerExtension { const uid = uuid.v4().replace(/-/g, ""); // insert a tracking pixel into the message - const serverUrl = `http://${PLUGIN_URL}/open/${draft.accountId}/${uid}`; + const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${uid}`; const img = ``; const draftBody = new DraftBody(draft); draftBody.unquoted = draftBody.unquoted + "
" + img; From 90b9570f91214f94bae44416aae13235ca0bb431 Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Wed, 24 Feb 2016 11:19:03 -0800 Subject: [PATCH 05/30] fix(send-later): Resolves a variety of small bugs from QA --- .../composer-translate/lib/main.cjsx | 1 + .../send-later/lib/send-later-popover.jsx | 74 +++++++++++++------ .../send-later/lib/send-later-store.js | 20 +++-- .../send-later/stylesheets/send-later.less | 11 +-- .../thread-snooze/lib/snooze-popover-body.jsx | 2 +- src/date-utils.es6 | 2 +- 6 files changed, 71 insertions(+), 39 deletions(-) diff --git a/internal_packages/composer-translate/lib/main.cjsx b/internal_packages/composer-translate/lib/main.cjsx index 09f721706..04743585a 100644 --- a/internal_packages/composer-translate/lib/main.cjsx +++ b/internal_packages/composer-translate/lib/main.cjsx @@ -61,6 +61,7 @@ class TranslateButton extends React.Component
item } itemContent={ (item) -> item } + defaultSelectedIndex={-1} onSelect={@_onTranslate} /> diff --git a/internal_packages/send-later/lib/send-later-popover.jsx b/internal_packages/send-later/lib/send-later-popover.jsx index 4aae2e975..f2d224dfe 100644 --- a/internal_packages/send-later/lib/send-later-popover.jsx +++ b/internal_packages/send-later/lib/send-later-popover.jsx @@ -23,7 +23,7 @@ class SendLaterPopover extends Component { static displayName = 'SendLaterPopover'; static propTypes = { - draftClientId: PropTypes.string, + draftClientId: PropTypes.string.isRequired, }; constructor(props) { @@ -38,9 +38,15 @@ class SendLaterPopover extends Component { this._subscription = Rx.Observable.fromQuery( DatabaseStore.findBy(Message, {clientId: this.props.draftClientId}) ).subscribe((draft)=> { - const scheduledDate = SendLaterStore.getScheduledDateForMessage(draft); - if (scheduledDate !== this.state.scheduledDate) { - this.setState({scheduledDate}); + const nextScheduledDate = SendLaterStore.getScheduledDateForMessage(draft); + + if (nextScheduledDate !== this.state.scheduledDate) { + const isPopout = (NylasEnv.getWindowType() === "composer"); + const isFinishedSelecting = ((this.state.scheduledDate === 'saving') && (nextScheduledDate !== null)); + if (isPopout && isFinishedSelecting) { + NylasEnv.close(); + } + this.setState({scheduledDate: nextScheduledDate}); } }); } @@ -51,29 +57,52 @@ class SendLaterPopover extends Component { onSelectMenuOption = (optionKey)=> { const date = SendLaterOptions[optionKey](); - const formatted = DateUtils.format(date.utc()) - - SendLaterActions.sendLater(this.props.draftClientId, formatted) - this.setState({scheduledDate: 'saving', inputDate: null}) - this.refs.popover.close() + const formatted = DateUtils.format(date.utc()); + this.onSelectDate(date); }; + onSelectCustomOption = (value)=> { + const date = DateUtils.fromString(value); + if (date) { + this.onSelectDate(date); + } else { + NylasEnv.showErrorDialog(`Sorry, we can't parse ${value} as a valid date.`); + } + } + + onSelectDate = (date)=> { + const formatted = DateUtils.format(date.utc()); + SendLaterActions.sendLater(this.props.draftClientId, formatted); + this.setState({scheduledDate: 'saving', inputDate: null}); + this.refs.popover.close(); + } + onCancelSendLater = ()=> { - SendLaterActions.cancelSendLater(this.props.draftClientId) - this.setState({inputDate: null}) - this.refs.popover.close() + SendLaterActions.cancelSendLater(this.props.draftClientId); + this.setState({inputDate: null}); + this.refs.popover.close(); }; renderCustomTimeSection() { - const updateInputDateValue = _.debounce((value)=> { - this.setState({inputDate: DateUtils.fromString(value)}) - }, 250); + const onChange = (event)=> { + this.setState({inputDate: DateUtils.fromString(event.target.value)}); + } + + const onKeyDown = (event)=> { + // we need to swallow these events so they don't reach the menu + // containing the text input, but only when you've typed something. + const val = event.target.value; + if ((val.length > 0) && ((event.keyCode === 13) || (event.keyCode === 39))) { + this.onSelectCustomOption(val); + event.stopPropagation(); + } + }; let dateInterpretation = false; if (this.state.inputDate) { - dateInterpretation = ( + dateInterpretation = ( {DateUtils.format(this.state.inputDate, DATE_FORMAT_LONG)} - ); + ); } return ( @@ -81,8 +110,9 @@ class SendLaterPopover extends Component { updateInputDateValue(event.target.value)}/> + placeholder="Or, 'next Monday at 2PM'" + onKeyDown={onKeyDown} + onChange={onChange}/> {dateInterpretation} ) @@ -92,7 +122,7 @@ class SendLaterPopover extends Component { const date = SendLaterOptions[optionKey](); const formatted = DateUtils.format(date, DATE_FORMAT_SHORT); return ( -
{optionKey}{formatted}
+
{optionKey}{formatted}
); } @@ -152,9 +182,11 @@ class SendLaterPopover extends Component { style={{order: -103}} className="send-later" buttonComponent={this.renderButton()}> -
item } itemContent={this.renderMenuOption} + defaultSelectedIndex={-1} footerComponents={footerComponents} onSelect={this.onSelectMenuOption} /> diff --git a/internal_packages/send-later/lib/send-later-store.js b/internal_packages/send-later/lib/send-later-store.js index c6386ade4..92f8366e8 100644 --- a/internal_packages/send-later/lib/send-later-store.js +++ b/internal_packages/send-later/lib/send-later-store.js @@ -28,32 +28,30 @@ class SendLaterStore extends NylasStore { }; setMetadata = (draftClientId, metadata)=> { - return ( - DatabaseStore.modelify(Message, [draftClientId]) - .then((messages)=> { - const {accountId} = messages[0]; - return NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId); - }) + DatabaseStore.modelify(Message, [draftClientId]).then((messages)=> { + const {accountId} = messages[0]; + + NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId) .then(()=> { Actions.setMetadata(messages, this.pluginId, metadata); }) .catch((error)=> { NylasEnv.reportError(error); NylasEnv.showErrorDialog(`Sorry, we were unable to schedule this message. ${error.message}`); - }) - ); + }); + }); }; onSendLater = (draftClientId, sendLaterDate)=> { - this.setMetadata(draftClientId, {sendLaterDate}) + this.setMetadata(draftClientId, {sendLaterDate}); }; onCancelSendLater = (draftClientId)=> { - this.setMetadata(draftClientId, {sendLaterDate: null}) + this.setMetadata(draftClientId, {sendLaterDate: null}); }; deactivate = ()=> { - this.unsubscribers.forEach(unsub => unsub()) + this.unsubscribers.forEach(unsub => unsub()); }; } diff --git a/internal_packages/send-later/stylesheets/send-later.less b/internal_packages/send-later/stylesheets/send-later.less index 18b9ced00..837c4fd4d 100644 --- a/internal_packages/send-later/stylesheets/send-later.less +++ b/internal_packages/send-later/stylesheets/send-later.less @@ -2,7 +2,7 @@ .send-later { - em { + .time { font-size: 0.9em; opacity: 0.6; } @@ -18,13 +18,14 @@ border-top-left-radius: @border-radius-base; } .item { - em { + .time { display: none; float: right; padding-right: @padding-base-horizontal; } + &.selected, &:hover { - em { + .time { display: inline-block; } } @@ -35,7 +36,7 @@ } .custom-time-section { padding: @padding-base-vertical @padding-base-horizontal; - em { + .time { color: @text-color-subtle; } } @@ -59,7 +60,7 @@ display: flex; align-items: center; - em { + .time { font-size: 0.9em; opacity: 0.62; } diff --git a/internal_packages/thread-snooze/lib/snooze-popover-body.jsx b/internal_packages/thread-snooze/lib/snooze-popover-body.jsx index 113062c28..b8724833c 100644 --- a/internal_packages/thread-snooze/lib/snooze-popover-body.jsx +++ b/internal_packages/thread-snooze/lib/snooze-popover-body.jsx @@ -139,7 +139,7 @@ class SnoozePopoverBody extends Component { diff --git a/src/date-utils.es6 b/src/date-utils.es6 index 3034060cc..3931be1c9 100644 --- a/src/date-utils.es6 +++ b/src/date-utils.es6 @@ -82,7 +82,7 @@ const DateUtils = { /** * Can take almost any string. - * e.g. "Next monday at 2pm" + * e.g. "Next Monday at 2pm" * @param {string} dateLikeString - a string representing a date. * @return {moment} - moment object representing date */ From 9dd7a9600aee862ce2e50d2b58f3634e0cc2c12d Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 12:24:19 -0800 Subject: [PATCH 06/30] fix(tracking): fix link tracking and read receipt plugins Fix broken links in link tracking and read receipts Fix bug in email frame where it wouldn't adjust the height even when content changed MessageItem bodies automatically clear the MessageBodyProcessor cache when the message contents (including metadata) change. Remove unused Account stuff from nylas-observables Plugins appIds properly read if there's an environment set --- .../lib/link-tracking-composer-extension.es6 | 1 + .../lib/link-tracking-message-extension.es6 | 4 ++-- internal_packages/link-tracking/lib/main.es6 | 2 +- .../message-list/lib/email-frame.cjsx | 15 +++++---------- .../message-list/lib/message-item-body.cjsx | 15 ++++++++++++--- .../spec/message-item-body-spec.cjsx | 2 +- .../open-tracking/lib/open-tracking-button.jsx | 1 - .../lib/open-tracking-message-status.jsx | 4 ++-- .../open-tracking/stylesheets/main.less | 2 -- spec/spec-helper.coffee | 1 + src/flux/stores/message-body-processor.coffee | 18 +++++++++++------- src/global/nylas-observables.coffee | 7 ------- src/package.coffee | 12 +++++++++++- 13 files changed, 47 insertions(+), 37 deletions(-) diff --git a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 index 8695109a5..5aa933275 100644 --- a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 @@ -29,6 +29,7 @@ export default class LinkTrackingComposerExtension extends ComposerExtension { // loop through all elements, replace with redirect links and save mappings draftBody.unquoted = draftBody.unquoted.replace(RegExpUtils.linkTagRegex(), (match, prefix, url, suffix, content, closingTag) => { const encoded = encodeURIComponent(url); + // the links param is an index of the link array. const redirectUrl = `${PLUGIN_URL}/link/${draft.accountId}/${messageUid}/${links.length}?redirect=${encoded}`; links.push({url: url, click_count: 0, click_data: [], redirect_url: redirectUrl}); return prefix + redirectUrl + suffix + content + closingTag; diff --git a/internal_packages/link-tracking/lib/link-tracking-message-extension.es6 b/internal_packages/link-tracking/lib/link-tracking-message-extension.es6 index 04d343295..d163def20 100644 --- a/internal_packages/link-tracking/lib/link-tracking-message-extension.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-message-extension.es6 @@ -1,9 +1,9 @@ import {MessageViewExtension, RegExpUtils} from 'nylas-exports' -import plugin from '../package.json' +import {PLUGIN_ID} from './link-tracking-constants' export default class LinkTrackingMessageExtension extends MessageViewExtension { static formatMessageBody({message}) { - const metadata = message.metadataForPluginId(plugin.appId) || {}; + const metadata = message.metadataForPluginId(PLUGIN_ID) || {}; if ((metadata.links || []).length === 0) { return } const links = {} for (const link of metadata.links) { diff --git a/internal_packages/link-tracking/lib/main.es6 b/internal_packages/link-tracking/lib/main.es6 index fb3007459..43d1ae0b0 100644 --- a/internal_packages/link-tracking/lib/main.es6 +++ b/internal_packages/link-tracking/lib/main.es6 @@ -31,7 +31,7 @@ function afterDraftSend({draftClientId}) { body: JSON.stringify(data), }).then( ([response, responseBody]) => { if (response.statusCode !== 200) { - throw new Error(); + throw new Error(`Link Tracking server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); } return responseBody; }).catch(error => { diff --git a/internal_packages/message-list/lib/email-frame.cjsx b/internal_packages/message-list/lib/email-frame.cjsx index d7918d9f1..b116fd9f9 100644 --- a/internal_packages/message-list/lib/email-frame.cjsx +++ b/internal_packages/message-list/lib/email-frame.cjsx @@ -1,7 +1,7 @@ React = require 'react' _ = require "underscore" {EventedIFrame} = require 'nylas-component-kit' -{QuotedHTMLTransformer} = require 'nylas-exports' +{Utils, QuotedHTMLTransformer} = require 'nylas-exports' EmailFrameStylesStore = require './email-frame-styles-store' @@ -11,9 +11,6 @@ class EmailFrame extends React.Component @propTypes: content: React.PropTypes.string.isRequired - constructor: (@props) -> - @_lastComputedHeight = 0 - render: => @@ -29,14 +26,12 @@ class EmailFrame extends React.Component componentDidUpdate: => @_writeContent() - shouldComponentUpdate: (newProps, newState) => - # Turns out, React is not able to tell if props.children has changed, - # so whenever the message list updates each email-frame is repopulated, - # often with the exact same content. To avoid unnecessary calls to - # _writeContent, we do a quick check for deep equality. - !_.isEqual(newProps, @props) + shouldComponentUpdate: (nextProps, nextState) => + not Utils.isEqualReact(nextProps, @props) or + not Utils.isEqualReact(nextState, @state) _writeContent: => + @_lastComputedHeight = 0 domNode = React.findDOMNode(@) doc = domNode.contentDocument return unless doc diff --git a/internal_packages/message-list/lib/message-item-body.cjsx b/internal_packages/message-list/lib/message-item-body.cjsx index b3c0fa8cb..d911c2d43 100644 --- a/internal_packages/message-list/lib/message-item-body.cjsx +++ b/internal_packages/message-list/lib/message-item-body.cjsx @@ -22,11 +22,20 @@ class MessageItemBody extends React.Component processedBody: undefined componentWillMount: => - @_unsub = MessageBodyProcessor.processAndSubscribe(@props.message, @_onBodyChanged) + @_prepareBody(@props) - componentWillReceiveProps: (newProps) => + shouldComponentUpdate: (nextProps, nextState) -> + not Utils.isEqualReact(nextProps, @props) or + not Utils.isEqualReact(nextState, @state) + + componentWillUpdate: (nextProps, nextState) => + if not Utils.isEqualReact(nextProps.message, @props.message) + @_prepareBody(nextProps) + + _prepareBody: (props) -> + MessageBodyProcessor.resetCache(props.message) @_unsub?() - @_unsub = MessageBodyProcessor.processAndSubscribe(newProps.message, @_onBodyChanged) + @_unsub = MessageBodyProcessor.processAndSubscribe(props.message, @_onBodyChanged) componentWillUnmount: => @_unsub?() diff --git a/internal_packages/message-list/spec/message-item-body-spec.cjsx b/internal_packages/message-list/spec/message-item-body-spec.cjsx index 725903724..c063af545 100644 --- a/internal_packages/message-list/spec/message-item-body-spec.cjsx +++ b/internal_packages/message-list/spec/message-item-body-spec.cjsx @@ -106,7 +106,7 @@ describe "MessageItem", -> snippet: "snippet one..." subject: "Subject One" threadId: "thread_12345" - accountId: TEST_ACCOUNT_ID + accountId: window.TEST_ACCOUNT_ID # Generate the test component. Should be called after @message is configured # for the test, since MessageItem assumes attributes of the message will not diff --git a/internal_packages/open-tracking/lib/open-tracking-button.jsx b/internal_packages/open-tracking/lib/open-tracking-button.jsx index b379c5707..10bf00935 100644 --- a/internal_packages/open-tracking/lib/open-tracking-button.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-button.jsx @@ -2,7 +2,6 @@ import {DraftStore, React, Actions, NylasAPI, DatabaseStore, Message, Rx} from ' import {RetinaImg} from 'nylas-component-kit' import {PLUGIN_ID, PLUGIN_NAME} from './open-tracking-constants' - export default class OpenTrackingButton extends React.Component { static displayName = 'OpenTrackingButton'; diff --git a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx index 4ac755ca3..4e124ab96 100644 --- a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx @@ -1,6 +1,6 @@ import {React} from 'nylas-exports' import {RetinaImg} from 'nylas-component-kit' -import plugin from '../package.json' +import {PLUGIN_ID} from './open-tracking-constants' export default class OpenTrackingMessageStatus extends React.Component { static displayName = "OpenTrackingMessageStatus"; @@ -15,7 +15,7 @@ export default class OpenTrackingMessageStatus extends React.Component { } _getStateFromMessage(message) { - const metadata = message.metadataForPluginId(plugin.appId); + const metadata = message.metadataForPluginId(PLUGIN_ID); if (!metadata) { return {hasMetadata: false, opened: false} } diff --git a/internal_packages/open-tracking/stylesheets/main.less b/internal_packages/open-tracking/stylesheets/main.less index 051bea281..82dd0ec7d 100644 --- a/internal_packages/open-tracking/stylesheets/main.less +++ b/internal_packages/open-tracking/stylesheets/main.less @@ -19,8 +19,6 @@ font-weight: bold; } .open-tracking-icon { - height: 10px; - margin: -9px 0 0 4px; } .read-receipt-message-status { diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 377e41230..0599c3abb 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -109,6 +109,7 @@ window.TEST_ACCOUNT_CLIENT_ID = "local-test-account-client-id" window.TEST_ACCOUNT_ID = "test-account-server-id" window.TEST_ACCOUNT_EMAIL = "tester@nylas.com" window.TEST_ACCOUNT_NAME = "Nylas Test" +window.TEST_PLUGIN_ID = "test-plugin-id-123" beforeEach -> NylasEnv.testOrganizationUnit = null diff --git a/src/flux/stores/message-body-processor.coffee b/src/flux/stores/message-body-processor.coffee index ccae31f08..2bacbbc96 100644 --- a/src/flux/stores/message-body-processor.coffee +++ b/src/flux/stores/message-body-processor.coffee @@ -11,13 +11,17 @@ class MessageBodyProcessor @_subscriptions = [] @resetCache() - resetCache: -> - # Store an object for recently processed items. Put the item reference into - # both data structures so we can access it in O(1) and also delete in O(1) - @_recentlyProcessedA = [] - @_recentlyProcessedD = {} - for {message, callback} in @_subscriptions - callback(@process(message)) + resetCache: (msg) -> + if msg + key = @_key(msg) + delete @_recentlyProcessedD[key] + else + # Store an object for recently processed items. Put the item reference into + # both data structures so we can access it in O(1) and also delete in O(1) + @_recentlyProcessedA = [] + @_recentlyProcessedD = {} + for {message, callback} in @_subscriptions + callback(@process(message)) # It's far safer to key off the hash of the body then the [id, version] # pair. This is because it's theoretically possible for the body to diff --git a/src/global/nylas-observables.coffee b/src/global/nylas-observables.coffee index dabe08c0d..ddf1a09b6 100644 --- a/src/global/nylas-observables.coffee +++ b/src/global/nylas-observables.coffee @@ -2,13 +2,8 @@ Rx = require 'rx-lite' _ = require 'underscore' Category = require '../flux/models/category' QuerySubscriptionPool = require '../flux/models/query-subscription-pool' -AccountStore = require '../flux/stores/account-store' DatabaseStore = require '../flux/stores/database-store' -AccountOperators = {} - -AccountObservables = {} - CategoryOperators = sort: -> obs = @.map (categories) -> @@ -60,10 +55,8 @@ CategoryObservables = CategoryObservables.forAccount(account).sort() .categoryFilter (cat) -> cat.isHiddenCategory() - module.exports = Categories: CategoryObservables - Accounts: AccountObservables # Attach a few global helpers diff --git a/src/package.coffee b/src/package.coffee index 939d389b1..fff3cf4f9 100644 --- a/src/package.coffee +++ b/src/package.coffee @@ -75,7 +75,17 @@ class Package @metadata ?= Package.loadMetadata(@path) @bundledPackage = Package.isBundledPackagePath(@path) @name = @metadata?.name ? path.basename(@path) - @pluginAppId = @metadata.appId ? null + + if @metadata.appId + if _.isString @metadata.appId + @pluginAppId = @metadata.appId ? null + else if _.isObject @metadata.appId + @pluginAppId = @metadata.appId[NylasEnv.config.get('env')] ? null + else + @pluginAppId = null + else + @pluginAppId = null + @displayName = @metadata?.displayName || @name ModuleCache.add(@path, @metadata) @reset() From ada4256dc82bc9a61c576f82532bb3a1ad41b9a8 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 12:31:14 -0800 Subject: [PATCH 07/30] fix(lint): fix less and js linter issues --- .../composer-emojis/stylesheets/composer-emojis.less | 2 +- internal_packages/send-later/lib/send-later-popover.jsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/internal_packages/composer-emojis/stylesheets/composer-emojis.less b/internal_packages/composer-emojis/stylesheets/composer-emojis.less index 146a103ec..124d967a2 100644 --- a/internal_packages/composer-emojis/stylesheets/composer-emojis.less +++ b/internal_packages/composer-emojis/stylesheets/composer-emojis.less @@ -6,7 +6,7 @@ overflow: auto; .btn.btn-icon { font-size: 14px !important; - padding: 0em 0.5em; + padding: 0 0.5em; &:first-child { padding-left: 0.5em !important; } diff --git a/internal_packages/send-later/lib/send-later-popover.jsx b/internal_packages/send-later/lib/send-later-popover.jsx index f2d224dfe..9bce2c29d 100644 --- a/internal_packages/send-later/lib/send-later-popover.jsx +++ b/internal_packages/send-later/lib/send-later-popover.jsx @@ -1,5 +1,4 @@ /** @babel */ -import _ from 'underscore' import Rx from 'rx-lite' import React, {Component, PropTypes} from 'react' import {DateUtils, Message, DatabaseStore} from 'nylas-exports' @@ -57,7 +56,6 @@ class SendLaterPopover extends Component { onSelectMenuOption = (optionKey)=> { const date = SendLaterOptions[optionKey](); - const formatted = DateUtils.format(date.utc()); this.onSelectDate(date); }; From 4158e598e0767f1141e9f0f43715539d714938fd Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 12:55:00 -0800 Subject: [PATCH 08/30] fix(tracking): tracking pixel won't register if it's from you --- .../message-list/lib/plugins/tracking-pixels-extension.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal_packages/message-list/lib/plugins/tracking-pixels-extension.coffee b/internal_packages/message-list/lib/plugins/tracking-pixels-extension.coffee index 1e6ee6486..44e2faf64 100644 --- a/internal_packages/message-list/lib/plugins/tracking-pixels-extension.coffee +++ b/internal_packages/message-list/lib/plugins/tracking-pixels-extension.coffee @@ -96,6 +96,10 @@ TrackingBlacklist = [{ name: 'Salesloft', pattern: 'sdr.salesloft.com/email_trackers', homepage: 'http://salesloft.com' + }, { + name: 'Nylas', + pattern: 'nylas.com/open', + homepage: 'http://nylas.com/N1' }] class TrackingPixelsExtension extends MessageViewExtension From 29785c209e9ab295e47a2eafa5b3a6313c7bf30a Mon Sep 17 00:00:00 2001 From: Drew Regitsky Date: Wed, 24 Feb 2016 13:02:18 -0800 Subject: [PATCH 09/30] fix(read-receipts): threadlist icon vert align and placeholder when missing --- internal_packages/open-tracking/lib/open-tracking-icon.jsx | 3 +-- internal_packages/open-tracking/stylesheets/main.less | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal_packages/open-tracking/lib/open-tracking-icon.jsx b/internal_packages/open-tracking/lib/open-tracking-icon.jsx index 578bb7061..d16f6e265 100644 --- a/internal_packages/open-tracking/lib/open-tracking-icon.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-icon.jsx @@ -39,11 +39,10 @@ export default class OpenTrackingIcon extends React.Component { } render() { - if (!this.state.hasMetadata) { return false } const title = this.state.opened ? "This message has been read at least once" : "This message has not been read"; return (
- {this._renderImage()} + {this.state.hasMetadata ? this._renderImage() : ""}
); } diff --git a/internal_packages/open-tracking/stylesheets/main.less b/internal_packages/open-tracking/stylesheets/main.less index 82dd0ec7d..9294dfdb2 100644 --- a/internal_packages/open-tracking/stylesheets/main.less +++ b/internal_packages/open-tracking/stylesheets/main.less @@ -3,7 +3,6 @@ .open-tracking-icon img.content-mask.unopened { background-color: #6b777d; - vertical-align: text-bottom; } .open-tracking-icon img.content-mask.opened { background-color: @text-color-link; @@ -19,6 +18,7 @@ font-weight: bold; } .open-tracking-icon { + width: 15px; } .read-receipt-message-status { From ecf3909f7d3cd1fc1148e0347e26e1e96ff66466 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 13:12:07 -0800 Subject: [PATCH 10/30] fix(read-receipts): fix padding and styles under selection --- .../open-tracking/stylesheets/main.less | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal_packages/open-tracking/stylesheets/main.less b/internal_packages/open-tracking/stylesheets/main.less index 9294dfdb2..6af1c99f6 100644 --- a/internal_packages/open-tracking/stylesheets/main.less +++ b/internal_packages/open-tracking/stylesheets/main.less @@ -1,12 +1,26 @@ @import "ui-variables"; @import "ui-mixins"; +.open-tracking-icon img { + vertical-align: initial; +} + .open-tracking-icon img.content-mask.unopened { - background-color: #6b777d; + background-color: fadeout(@text-color, 80%); } .open-tracking-icon img.content-mask.opened { background-color: @text-color-link; } + +.list-item.focused { + .open-tracking-icon img.content-mask.unopened { + background-color: fadeout(@text-color-inverse, 70%); + } + .open-tracking-icon img.content-mask.opened { + background-color: @background-off-primary; + } +} + .open-tracking-icon .open-count { display: inline-block; position: relative; @@ -17,8 +31,10 @@ font-size: 12px; font-weight: bold; } + .open-tracking-icon { width: 15px; + padding-left: @padding-base-horizontal - 4; } .read-receipt-message-status { From 030d86e3bd121f7358d01024f1c6d63c56695c9b Mon Sep 17 00:00:00 2001 From: Ben Gotow Date: Wed, 24 Feb 2016 13:16:06 -0800 Subject: [PATCH 11/30] fix(snooze/send-later): Change chrono config to always prefer dates in future --- .../send-later/lib/send-later-popover.jsx | 10 ++-- .../thread-snooze/lib/snooze-popover-body.jsx | 2 +- src/date-utils.es6 | 49 ++++++++++++++++++- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/internal_packages/send-later/lib/send-later-popover.jsx b/internal_packages/send-later/lib/send-later-popover.jsx index 9bce2c29d..fb846dded 100644 --- a/internal_packages/send-later/lib/send-later-popover.jsx +++ b/internal_packages/send-later/lib/send-later-popover.jsx @@ -60,7 +60,7 @@ class SendLaterPopover extends Component { }; onSelectCustomOption = (value)=> { - const date = DateUtils.fromString(value); + const date = DateUtils.futureDateFromString(value); if (date) { this.onSelectDate(date); } else { @@ -83,7 +83,7 @@ class SendLaterPopover extends Component { renderCustomTimeSection() { const onChange = (event)=> { - this.setState({inputDate: DateUtils.fromString(event.target.value)}); + this.setState({inputDate: DateUtils.futureDateFromString(event.target.value)}); } const onKeyDown = (event)=> { @@ -130,7 +130,7 @@ class SendLaterPopover extends Component { if (scheduledDate === 'saving') { return ( - + ) } } diff --git a/internal_packages/open-tracking/lib/open-tracking-button.jsx b/internal_packages/open-tracking/lib/open-tracking-button.jsx index 10bf00935..34e24c14f 100644 --- a/internal_packages/open-tracking/lib/open-tracking-button.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-button.jsx @@ -1,60 +1,35 @@ -import {DraftStore, React, Actions, NylasAPI, DatabaseStore, Message, Rx} from 'nylas-exports' -import {RetinaImg} from 'nylas-component-kit' +// import {DraftStore, React, Actions, NylasAPI, DatabaseStore, Message, Rx} from 'nylas-exports' +import {React} from 'nylas-exports' +import {MetadataComposerToggleButton} from 'nylas-component-kit' import {PLUGIN_ID, PLUGIN_NAME} from './open-tracking-constants' export default class OpenTrackingButton extends React.Component { - static displayName = 'OpenTrackingButton'; static propTypes = { draftClientId: React.PropTypes.string.isRequired, }; - constructor(props) { - super(props); - this.state = {enabled: false}; + _title(enabled) { + const dir = enabled ? "Disable" : "Enable"; + return `${dir} read receipts` } - componentDidMount() { - const query = DatabaseStore.findBy(Message, {clientId: this.props.draftClientId}); - this._subscription = Rx.Observable.fromQuery(query).subscribe(this.setStateFromDraft) + _errorMessage(error) { + return `Sorry, we were unable to save your read receipt settings. ${error.message}` } - componentWillUnmount() { - this._subscription.dispose(); - } - - setStateFromDraft = (draft)=> { - if (!draft) return; - const metadata = draft.metadataForPluginId(PLUGIN_ID); - this.setState({enabled: metadata ? metadata.tracked : false}); - }; - - _onClick=()=> { - const currentlyEnabled = this.state.enabled; - - // write metadata into the draft to indicate tracked state - DraftStore.sessionForClientId(this.props.draftClientId).then((session)=> { - const draft = session.draft(); - - NylasAPI.authPlugin(PLUGIN_ID, PLUGIN_NAME, draft.accountId) - .then(() => { - Actions.setMetadata(draft, PLUGIN_ID, currentlyEnabled ? null : {tracked: true}); - }) - .catch((error)=> { - NylasEnv.reportError(error); - NylasEnv.showErrorDialog(`Sorry, we were unable to save your read receipt settings. ${error.message}`); - }); - }); - }; - render() { - const title = this.state.enabled ? "Disable" : "Enable"; - return () + return ( + + ) } - } diff --git a/src/components/metadata-composer-toggle-button.jsx b/src/components/metadata-composer-toggle-button.jsx new file mode 100644 index 000000000..8d937c650 --- /dev/null +++ b/src/components/metadata-composer-toggle-button.jsx @@ -0,0 +1,123 @@ +import {DraftStore, React, Actions, NylasAPI, DatabaseStore, Message, Rx} from 'nylas-exports' +import {RetinaImg} from 'nylas-component-kit' +import classnames from 'classnames' + +export default class MetadataComposerToggleButton extends React.Component { + + static displayName = 'MetadataComposerToggleButton'; + + static propTypes = { + title: React.PropTypes.func.isRequired, + iconUrl: React.PropTypes.string, + iconName: React.PropTypes.string, + pluginId: React.PropTypes.string.isRequired, + pluginName: React.PropTypes.string.isRequired, + metadataKey: React.PropTypes.string.isRequired, + stickyToggle: React.PropTypes.bool, + errorMessage: React.PropTypes.func.isRequired, + draftClientId: React.PropTypes.string.isRequired, + }; + + static defaultProps = { + stickyToggle: false, + } + + constructor(props) { + super(props); + this.state = { + enabled: false, + isSetup: false, + }; + } + + componentDidMount() { + this._mounted = true; + const query = DatabaseStore.findBy(Message, {clientId: this.props.draftClientId}); + this._subscription = Rx.Observable.fromQuery(query).subscribe(this._onDraftChange) + } + + componentWillUnmount() { + this._mounted = false + this._subscription.dispose(); + } + + _configKey() { + return `plugins.${this.props.pluginId}.defaultOn` + } + + _isDefaultOn() { + return NylasEnv.config.get(this._configKey()) + } + + _onDraftChange = (draft)=> { + if (!this._mounted || !draft) { return; } + const metadata = draft.metadataForPluginId(this.props.pluginId); + if (!metadata) { + if (!this.state.isSetup) { + if (this._isDefaultOn()) { + this._setMetadataValueTo(true) + } + this.setState({isSetup: true}) + } + } else { + this.setState({enabled: metadata.tracked, isSetup: true}); + } + }; + + _setMetadataValueTo(enabled) { + const newValue = {} + newValue[this.props.metadataKey] = enabled + this.setState({enabled, pending: true}); + const metadataValue = enabled ? newValue : null + // write metadata into the draft to indicate tracked state + return DraftStore.sessionForClientId(this.props.draftClientId).then((session)=> { + const draft = session.draft(); + + return NylasAPI.authPlugin(this.props.pluginId, this.props.pluginName, draft.accountId) + .then(() => { + Actions.setMetadata(draft, this.props.pluginId, metadataValue); + }) + .catch((error) => { + this.setState({enabled: false}); + NylasEnv.reportError(error); + NylasEnv.showErrorDialog(this.props.errorMessage(error)); + }) + }).finally(() => { + this.setState({pending: false}) + }); + } + + _onClick = () => { + // Toggle. + if (this.state.pending) { return; } + if (this.props.stickyToggle) { + NylasEnv.config.set(this._configKey(), !this.state.enabled) + } + this._setMetadataValueTo(!this.state.enabled) + }; + + render() { + const title = this.props.title(this.state.enabled) + + const className = classnames({ + "btn": true, + "btn-toolbar": true, + "btn-pending": this.state.pending, + "btn-enabled": this.state.enabled, + }); + + const attrs = {} + if (this.props.iconUrl) { + attrs.url = this.props.iconUrl + } else if (this.props.iconName) { + attrs.name = this.props.iconName + } + + return ( + + ); + } + +} diff --git a/src/global/nylas-component-kit.coffee b/src/global/nylas-component-kit.coffee index 52fd65552..4ef18a29f 100644 --- a/src/global/nylas-component-kit.coffee +++ b/src/global/nylas-component-kit.coffee @@ -32,6 +32,7 @@ class NylasComponentKit @load "MultiselectActionBar", 'multiselect-action-bar' @load "InjectedComponentSet", 'injected-component-set' @load "TimeoutTransitionGroup", 'timeout-transition-group' + @load "MetadataComposerToggleButton", 'metadata-composer-toggle-button' @load "ConfigPropContainer", "config-prop-container" @load "DisclosureTriangle", "disclosure-triangle" @load "EditableList", "editable-list" From f7b1209b5cc91cf3e7d09239e57423a11ac918e0 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:01:44 -0800 Subject: [PATCH 16/30] fix(fixed-popover): Reposition when overflowing on `left` direction - This is a temporary solution --- .../lib/quick-action-snooze-button.jsx | 4 +-- src/components/fixed-popover.jsx | 28 +++++++++++++------ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx b/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx index a919d215e..54d717218 100644 --- a/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx +++ b/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx @@ -25,10 +25,10 @@ class QuickActionSnoozeButton extends Component { // Grab the parent node because of the zoom applied to this button. If we // took this element directly, we'd have to divide everything by 2 const element = React.findDOMNode(this).parentNode; - const {height, width, top, left} = element.getBoundingClientRect() + const {height, width, top, bottom, left, right} = element.getBoundingClientRect() // The parent node is a bit too much to the left, lets adjust this. - const rect = {height, width, top, left: left + 5} + const rect = {height, width, top, bottom, right, left: left + 5} Actions.openPopover( , rect, diff --git a/src/components/fixed-popover.jsx b/src/components/fixed-popover.jsx index d342e203a..18f8cba0e 100644 --- a/src/components/fixed-popover.jsx +++ b/src/components/fixed-popover.jsx @@ -2,6 +2,11 @@ import _ from 'underscore'; import React, {Component, PropTypes} from 'react'; +// TODO +// This is a temporary hack for the snooze popover +// This should be the actual dimensions of the rendered popover body +const OVERFLOW_LIMIT = 50; + /** * Renders a popover absultely positioned in the window next to the provided * rect. @@ -69,12 +74,8 @@ class FixedPopover extends Component { } }; - _getNewDirection = (direction, originRect, windowDimensions)=> { - // TODO - // This is a temporary solution for the snooze popover. - // This should grab the actual dimensions of the rendered popover body - const limit = 50; - + _getNewDirection = (direction, originRect, windowDimensions, limit = OVERFLOW_LIMIT)=> { + // TODO this is a hack. Implement proper repositioning switch (direction) { case 'right': if ( @@ -93,7 +94,6 @@ class FixedPopover extends Component { } break; default: - // TODO implement missing break; } return null; @@ -145,9 +145,19 @@ class FixedPopover extends Component { top: originRect.top, right: (windowDimensions.width - originRect.left) + 10, } + // TODO This is a hack for the snooze popover. Fix this + let popoverTop = originRect.height / 2; + let popoverTransform = 'translate(-100%, -50%)'; + if (originRect.top < OVERFLOW_LIMIT * 2) { + popoverTop = 0; + popoverTransform = 'translate(-100%, 0)'; + } else if (windowDimensions.height - originRect.bottom < OVERFLOW_LIMIT * 2) { + popoverTop = -190; + popoverTransform = 'translate(-100%, 0)'; + } popoverStyle = { - transform: 'translate(-100%, -50%)', - top: originRect.height / 2, + transform: popoverTransform, + top: popoverTop, } pointerStyle = { transform: 'translate(-13px, -50%) rotate(270deg)', From b09ae2d317a3cc8711d205cad2308e215869499f Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:02:25 -0800 Subject: [PATCH 17/30] fix(popover): Correctly position pointer --- src/components/fixed-popover.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fixed-popover.jsx b/src/components/fixed-popover.jsx index 18f8cba0e..a412ab3f5 100644 --- a/src/components/fixed-popover.jsx +++ b/src/components/fixed-popover.jsx @@ -174,7 +174,7 @@ class FixedPopover extends Component { top: originRect.height / 2, } pointerStyle = { - transform: 'translate(-12px, 0) rotate(90deg)', + transform: 'translate(-12px, -50%) rotate(90deg)', top: originRect.height, // Don't divide by 2 because of zoom } break; From bae079dcc643679b4c195ee29440c1c971f376e0 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:06:06 -0800 Subject: [PATCH 18/30] fix(snooze): Only allow snooze from inbox --- .../thread-list/lib/thread-list.cjsx | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/internal_packages/thread-list/lib/thread-list.cjsx b/internal_packages/thread-list/lib/thread-list.cjsx index 2297d58f7..ea7882680 100644 --- a/internal_packages/thread-list/lib/thread-list.cjsx +++ b/internal_packages/thread-list/lib/thread-list.cjsx @@ -125,30 +125,31 @@ class ThreadList extends React.Component Actions.queueTasks(tasks) callback(true) - props.onSwipeLeftClass = 'swipe-snooze' - props.onSwipeLeft = (callback) => - # TODO this should be grabbed from elsewhere - {PopoverStore} = require 'nylas-exports' - SnoozePopoverBody = require '../../thread-snooze/lib/snooze-popover-body' + if perspective.isInbox() + props.onSwipeLeftClass = 'swipe-snooze' + props.onSwipeLeft = (callback) => + # TODO this should be grabbed from elsewhere + {PopoverStore} = require 'nylas-exports' + SnoozePopoverBody = require '../../thread-snooze/lib/snooze-popover-body' - # TODO - # The question I want to ask is if I am already swiping, i.e. mid swipe, - # but I don't know how to ask it. - # This is good enough for now - if PopoverStore.isPopoverOpen() - callback(false) - return + # TODO + # The question I want to ask is if I am already swiping, i.e. mid swipe, + # but I don't know how to ask it. + # This is good enough for now + if PopoverStore.isPopoverOpen() + callback(false) + return - element = document.querySelector("[data-item-id=\"#{item.id}\"]") - rect = element.getBoundingClientRect() - Actions.openPopover( - , - rect, - "right" - ) + element = document.querySelector("[data-item-id=\"#{item.id}\"]") + rect = element.getBoundingClientRect() + Actions.openPopover( + , + rect, + "right" + ) props From 2eefbc28d3b34f3d12a1cf12b8629c61b735da3b Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:08:53 -0800 Subject: [PATCH 19/30] fix(snooze): Close popover when opening a new one --- internal_packages/thread-snooze/lib/snooze-popover.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal_packages/thread-snooze/lib/snooze-popover.jsx b/internal_packages/thread-snooze/lib/snooze-popover.jsx index 96a65681b..577938b5a 100644 --- a/internal_packages/thread-snooze/lib/snooze-popover.jsx +++ b/internal_packages/thread-snooze/lib/snooze-popover.jsx @@ -1,5 +1,6 @@ /** @babel */ import React, {Component, PropTypes} from 'react'; +import {Actions} from 'nylas-exports'; import {Popover} from 'nylas-component-kit'; import SnoozePopoverBody from './snooze-popover-body'; @@ -29,7 +30,8 @@ class SnoozePopover extends Component { direction={direction || 'down-align-left'} buttonComponent={buttonComponent} popoverStyle={popoverStyle} - pointerStyle={pointerStyle}> + pointerStyle={pointerStyle} + onOpened={()=> Actions.closePopover()}> ); From 6458a90edaaaf43aa73bfa618efe42140a221913 Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:11:03 -0800 Subject: [PATCH 20/30] fix(snooze): Close popover on error --- internal_packages/thread-snooze/lib/snooze-store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/internal_packages/thread-snooze/lib/snooze-store.js b/internal_packages/thread-snooze/lib/snooze-store.js index 9648e24ea..017d7905c 100644 --- a/internal_packages/thread-snooze/lib/snooze-store.js +++ b/internal_packages/thread-snooze/lib/snooze-store.js @@ -30,6 +30,7 @@ class SnoozeStore { }) }) .catch((error)=> { + Actions.closePopover(); NylasEnv.reportError(error); NylasEnv.showErrorDialog(`Sorry, we were unable to save your snooze settings. ${error.message}`); }); From 793941ef92d3e81ed433d420cd0407418b8131f7 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 15:25:11 -0800 Subject: [PATCH 21/30] fix(read-receipt): message status updates live --- .../open-tracking/lib/open-tracking-message-status.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx index 4e124ab96..556e57a2f 100644 --- a/internal_packages/open-tracking/lib/open-tracking-message-status.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-message-status.jsx @@ -14,6 +14,10 @@ export default class OpenTrackingMessageStatus extends React.Component { this.state = this._getStateFromMessage(props.message) } + componentWillReceiveProps(nextProps) { + this.setState(this._getStateFromMessage(nextProps.message)) + } + _getStateFromMessage(message) { const metadata = message.metadataForPluginId(PLUGIN_ID); if (!metadata) { From 3187d2f0d1ad58c1cd98178f9fdf144c837908ae Mon Sep 17 00:00:00 2001 From: Juan Tejada Date: Wed, 24 Feb 2016 15:33:13 -0800 Subject: [PATCH 22/30] fix(snooze): Fix quick action button, remove italic from popover --- .../thread-snooze/lib/quick-action-snooze-button.jsx | 9 ++++++--- .../thread-snooze/lib/snooze-popover-body.jsx | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx b/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx index 54d717218..c15cbc5fd 100644 --- a/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx +++ b/internal_packages/thread-snooze/lib/quick-action-snooze-button.jsx @@ -1,5 +1,5 @@ import React, {Component, PropTypes} from 'react'; -import {PopoverStore, Actions} from 'nylas-exports'; +import {Actions} from 'nylas-exports'; import SnoozePopoverBody from './snooze-popover-body'; @@ -12,12 +12,14 @@ class QuickActionSnoozeButton extends Component { constructor() { super(); + this.openedPopover = false; } onClick = (event)=> { event.stopPropagation() - if (PopoverStore.isPopoverOpen()) { + if (this.openedPopover) { Actions.closePopover(); + this.openedPopover = false; return; } const {thread} = this.props; @@ -30,10 +32,11 @@ class QuickActionSnoozeButton extends Component { // The parent node is a bit too much to the left, lets adjust this. const rect = {height, width, top, bottom, right, left: left + 5} Actions.openPopover( - , + , rect, "left" ) + this.openedPopover = true; }; static containerRequired = false; diff --git a/internal_packages/thread-snooze/lib/snooze-popover-body.jsx b/internal_packages/thread-snooze/lib/snooze-popover-body.jsx index da5c67b7c..48899add1 100644 --- a/internal_packages/thread-snooze/lib/snooze-popover-body.jsx +++ b/internal_packages/thread-snooze/lib/snooze-popover-body.jsx @@ -143,7 +143,7 @@ class SnoozePopoverBody extends Component { onMouseDown={this.onInputMouseDown} onKeyDown={this.onInputKeyDown} onChange={this.onInputChange}/> - {formatted} + {formatted} ); }; From f0e76019fddf551f4a9254ecd45b020787c93968 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Wed, 24 Feb 2016 16:05:16 -0800 Subject: [PATCH 23/30] fix(error-handling): handle offline for link tracking & read-receipts Fix offline error handling of link tracking and read receipts Fix ellipses for tooltips Allow you to change the title in the error box --- .../lib/template-picker.jsx | 2 +- .../composer-translate/lib/main.cjsx | 2 +- .../composer/lib/expanded-participants.cjsx | 2 +- .../lib/link-tracking-button.jsx | 7 ++++-- .../lib/open-tracking-button.jsx | 7 ++++-- .../quick-schedule/lib/calendar-button.cjsx | 2 +- .../send-later/lib/send-later-popover.jsx | 2 +- .../metadata-composer-toggle-button.jsx | 24 ++++++++++++++++--- src/nylas-env.coffee | 13 ++++++++-- 9 files changed, 47 insertions(+), 14 deletions(-) diff --git a/internal_packages/composer-templates/lib/template-picker.jsx b/internal_packages/composer-templates/lib/template-picker.jsx index e92ebe744..deff81100 100644 --- a/internal_packages/composer-templates/lib/template-picker.jsx +++ b/internal_packages/composer-templates/lib/template-picker.jsx @@ -64,7 +64,7 @@ class TemplatePicker extends React.Component { render() { const button = ( - diff --git a/internal_packages/send-later/lib/send-later-popover.jsx b/internal_packages/send-later/lib/send-later-popover.jsx index fb846dded..52024cb70 100644 --- a/internal_packages/send-later/lib/send-later-popover.jsx +++ b/internal_packages/send-later/lib/send-later-popover.jsx @@ -148,7 +148,7 @@ class SendLaterPopover extends Component { } } return ( -