diff --git a/internal_packages/link-tracking/lib/link-tracking-button.jsx b/internal_packages/link-tracking/lib/link-tracking-button.jsx index 9435e080a..df3d7b4d4 100644 --- a/internal_packages/link-tracking/lib/link-tracking-button.jsx +++ b/internal_packages/link-tracking/lib/link-tracking-button.jsx @@ -29,7 +29,7 @@ export default class LinkTrackingButton extends React.Component { iconName="icon-composer-linktracking.png" pluginId={PLUGIN_ID} pluginName={PLUGIN_NAME} - metadataKey="tracked" + metadataEnabledValue={{"tracked": true}} stickyToggle errorMessage={this._errorMessage} draftClientId={this.props.draftClientId} /> 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 5aa933275..e407a4631 100644 --- a/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 +++ b/internal_packages/link-tracking/lib/link-tracking-composer-extension.es6 @@ -42,6 +42,7 @@ export default class LinkTrackingComposerExtension extends ComposerExtension { // save the link info to draft metadata metadata.uid = messageUid; metadata.links = links; + Actions.setMetadata(draft, PLUGIN_ID, metadata); } } diff --git a/internal_packages/link-tracking/lib/link-tracking-icon.jsx b/internal_packages/link-tracking/lib/link-tracking-icon.jsx deleted file mode 100644 index f65f28e6a..000000000 --- a/internal_packages/link-tracking/lib/link-tracking-icon.jsx +++ /dev/null @@ -1,59 +0,0 @@ -import {React} from 'nylas-exports' -import {RetinaImg} from 'nylas-component-kit' -import {PLUGIN_ID} from './link-tracking-constants' - -const sum = (array, extractFn) => array.reduce( (a, b) => a + extractFn(b), 0 ); - - -export default class LinkTrackingIcon extends React.Component { - - static displayName = 'LinkTrackingIcon'; - - static propTypes = { - thread: React.PropTypes.object.isRequired, - }; - - constructor(props) { - super(props); - this.state = this._getStateFromThread(props.thread); - } - - componentWillReceiveProps(newProps) { - this.setState(this._getStateFromThread(newProps.thread)); - } - - _getStateFromThread(thread) { - const messages = thread.metadata; - // Pull a list of metadata for all messages - const metadataObjs = messages.map(msg => msg.metadataForPluginId(PLUGIN_ID)).filter(meta => meta); - if (metadataObjs.length) { - // If there's metadata, return the total number of link clicks in the most recent metadata - const mostRecentMetadata = metadataObjs.pop(); - return { - clicks: sum(mostRecentMetadata.links || [], link => link.click_count || 0), - }; - } - return {clicks: null}; - } - - - _renderIcon = () => { - return this.state.clicks == null ? "" : this._getIcon(this.state.clicks); - }; - - _getIcon(clicks) { - return ( - 0 ? "clicked" : ""} - name="icon-composer-linktracking.png" - mode={RetinaImg.Mode.ContentIsMask} /> - {clicks > 0 ? clicks : ""} - ) - } - - render() { - return (
- {this._renderIcon()} -
) - } -} diff --git a/internal_packages/link-tracking/lib/link-tracking-panel.jsx b/internal_packages/link-tracking/lib/link-tracking-panel.jsx deleted file mode 100644 index fa7a40331..000000000 --- a/internal_packages/link-tracking/lib/link-tracking-panel.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import {React} from 'nylas-exports' -import {PLUGIN_ID} from './link-tracking-constants' - - -export default class LinkTrackingPanel extends React.Component { - static displayName = 'LinkTrackingPanel'; - - static propTypes = { - message: React.PropTypes.object.isRequired, - }; - - constructor(props) { - super(props); - this.state = this._getStateFromMessage(props.message) - } - - componentWillReceiveProps(newProps) { - this.setState(this._getStateFromMessage(newProps.message)); - } - - _getStateFromMessage(message) { - const metadata = message.metadataForPluginId(PLUGIN_ID); - return metadata ? {links: metadata.links} : {}; - } - - _renderContents() { - return this.state.links.map(link => { - return ( - {link.url} - {link.click_count + " clicks"} - ) - }) - } - - render() { - if (this.state.links) { - return (
-

Link Tracking Enabled

- - - {this._renderContents()} - -
-
); - } - return
; - } -} diff --git a/internal_packages/link-tracking/lib/main.es6 b/internal_packages/link-tracking/lib/main.es6 index 43d1ae0b0..5fca88586 100644 --- a/internal_packages/link-tracking/lib/main.es6 +++ b/internal_packages/link-tracking/lib/main.es6 @@ -1,9 +1,6 @@ import request from 'request'; -import {ComponentRegistry, DatabaseStore, Message, ExtensionRegistry, Actions} from 'nylas-exports'; +import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports'; import LinkTrackingButton from './link-tracking-button'; -// TODO what's up with these components? -// import LinkTrackingIcon from './link-tracking-icon'; -// import LinkTrackingPanel from './link-tracking-panel'; import LinkTrackingComposerExtension from './link-tracking-composer-extension'; import LinkTrackingMessageExtension from './link-tracking-message-extension'; import {PLUGIN_ID, PLUGIN_URL} from './link-tracking-constants'; @@ -11,41 +8,36 @@ import {PLUGIN_ID, PLUGIN_URL} from './link-tracking-constants'; const post = Promise.promisify(request.post, {multiArgs: true}); -function afterDraftSend({draftClientId}) { +function afterDraftSend({message}) { // only run this handler in the main window if (!NylasEnv.isMainWindow()) return; - // query for the message - DatabaseStore.findBy(Message, {clientId: draftClientId}).then((message) => { - // grab message metadata, if any - const metadata = message.metadataForPluginId(PLUGIN_ID); - // get the uid from the metadata, if present - if (metadata) { - const uid = metadata.uid; + // grab message metadata, if any + const metadata = message.metadataForPluginId(PLUGIN_ID); + if (metadata) { + // get the uid from the metadata, if present + const uid = metadata.uid; - // post the uid and message id pair to the plugin server - const data = {uid: uid, message_id: message.id}; - const serverUrl = `${PLUGIN_URL}/plugins/register-message`; - return post({ - url: serverUrl, - body: JSON.stringify(data), - }).then( ([response, responseBody]) => { - if (response.statusCode !== 200) { - throw new Error(`Link Tracking server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); - } - return responseBody; - }).catch(error => { - NylasEnv.showErrorDialog("There was a problem contacting the Link Tracking server! This message will not have link tracking"); - Promise.reject(error); - }); - } - }); + // post the uid and message id pair to the plugin server + const data = {uid: uid, message_id: message.id}; + const serverUrl = `${PLUGIN_URL}/plugins/register-message`; + + post({ + url: serverUrl, + body: JSON.stringify(data), + }).then( ([response, responseBody]) => { + if (response.statusCode !== 200) { + throw new Error(`Link Tracking server error ${response.statusCode} at ${serverUrl}: ${responseBody}`); + } + }).catch(error => { + NylasEnv.showErrorDialog("There was a problem contacting the Link Tracking server! This message will not have link tracking"); + Promise.reject(error); + }); + } } export function activate() { ComponentRegistry.register(LinkTrackingButton, {role: 'Composer:ActionButton'}); - // ComponentRegistry.register(LinkTrackingIcon, {role: 'ThreadListIcon'}); - // ComponentRegistry.register(LinkTrackingPanel, {role: 'message:BodyHeader'}); ExtensionRegistry.Composer.register(LinkTrackingComposerExtension); ExtensionRegistry.MessageView.register(LinkTrackingMessageExtension); this._unlistenSendDraftSuccess = Actions.sendDraftSuccess.listen(afterDraftSend); @@ -55,8 +47,6 @@ export function serialize() {} export function deactivate() { ComponentRegistry.unregister(LinkTrackingButton); - // ComponentRegistry.unregister(LinkTrackingIcon); - // ComponentRegistry.unregister(LinkTrackingPanel); ExtensionRegistry.Composer.unregister(LinkTrackingComposerExtension); ExtensionRegistry.MessageView.unregister(LinkTrackingMessageExtension); this._unlistenSendDraftSuccess() diff --git a/internal_packages/open-tracking/lib/main.es6 b/internal_packages/open-tracking/lib/main.es6 index 0a3fcd418..820d1dab7 100644 --- a/internal_packages/open-tracking/lib/main.es6 +++ b/internal_packages/open-tracking/lib/main.es6 @@ -1,5 +1,5 @@ import request from 'request'; -import {ComponentRegistry, ExtensionRegistry, DatabaseStore, Message, Actions} from 'nylas-exports'; +import {ComponentRegistry, ExtensionRegistry, Actions} from 'nylas-exports'; import OpenTrackingButton from './open-tracking-button'; import OpenTrackingIcon from './open-tracking-icon'; import OpenTrackingMessageStatus from './open-tracking-message-status'; @@ -9,39 +9,33 @@ import {PLUGIN_ID, PLUGIN_URL} from './open-tracking-constants' const post = Promise.promisify(request.post, {multiArgs: true}); -function afterDraftSend({draftClientId}) { +function afterDraftSend({message}) { // only run this handler in the main window if (!NylasEnv.isMainWindow()) return; - // query for the message - DatabaseStore.findBy(Message, {clientId: draftClientId}).then((message) => { - // grab message metadata, if any - const metadata = message.metadataForPluginId(PLUGIN_ID); + // grab message metadata, if any + const metadata = message.metadataForPluginId(PLUGIN_ID); - // get the uid from the metadata, if present - if (metadata) { - const uid = metadata.uid; + // get the uid from the metadata, if present + if (metadata) { + const uid = metadata.uid; - // set metadata against the message - Actions.setMetadata(message, PLUGIN_ID, {open_count: 0, open_data: []}); + // 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}/plugins/register-message`; - // 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}/plugins/register-message`; - return post({ - url: serverUrl, - body: JSON.stringify(data), - }).then(([response, responseBody]) => { - if (response.statusCode !== 200) { - throw new Error(); - } - return responseBody; - }).catch(error => { - NylasEnv.showErrorDialog("There was a problem contacting the Open Tracking server! This message will not have open tracking :("); - Promise.reject(error); - }); - } - }); + post({ + url: serverUrl, + body: JSON.stringify(data), + }).then(([response, responseBody]) => { + if (response.statusCode !== 200) { + throw new Error(responseBody); + } + }).catch(error => { + NylasEnv.showErrorDialog(`There was a problem saving your open tracking settings. This message will not have open tracking. ${error.message}`); + Promise.reject(error); + }); + } } export function activate() { diff --git a/internal_packages/open-tracking/lib/open-tracking-button.jsx b/internal_packages/open-tracking/lib/open-tracking-button.jsx index d7a2aed8a..3fa74ebad 100644 --- a/internal_packages/open-tracking/lib/open-tracking-button.jsx +++ b/internal_packages/open-tracking/lib/open-tracking-button.jsx @@ -2,6 +2,7 @@ import {React, APIError, NylasAPI} from 'nylas-exports' import {MetadataComposerToggleButton} from 'nylas-component-kit' import {PLUGIN_ID, PLUGIN_NAME} from './open-tracking-constants' +import uuid from 'node-uuid'; export default class OpenTrackingButton extends React.Component { static displayName = 'OpenTrackingButton'; @@ -23,13 +24,19 @@ export default class OpenTrackingButton extends React.Component { } render() { + const enabledValue = { + uid: uuid.v4().replace(/-/g, ""), + open_count: 0, + open_data: [], + }; + return ( 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 d99ca9269..f4c45004a 100644 --- a/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 +++ b/internal_packages/open-tracking/lib/open-tracking-composer-extension.es6 @@ -1,5 +1,4 @@ -import uuid from 'node-uuid'; -import {ComposerExtension, Actions, QuotedHTMLTransformer} from 'nylas-exports'; +import {ComposerExtension, QuotedHTMLTransformer} from 'nylas-exports'; import {PLUGIN_ID, PLUGIN_URL} from './open-tracking-constants'; @@ -17,11 +16,8 @@ export default class OpenTrackingComposerExtension extends ComposerExtension { // grab message metadata, if any const metadata = draft.metadataForPluginId(PLUGIN_ID); if (metadata) { - // generate a UID - const uid = uuid.v4().replace(/-/g, ""); - // insert a tracking pixel into the message - const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${uid}`; + const serverUrl = `${PLUGIN_URL}/open/${draft.accountId}/${metadata.uid}`; const img = ``; const draftBody = new DraftBody(draft); draftBody.unquoted = draftBody.unquoted + "
" + img; @@ -29,10 +25,6 @@ export default class OpenTrackingComposerExtension extends ComposerExtension { // save the draft session.changes.add({body: draftBody.body}); session.changes.commit(); - - // save the uid to draft metadata - metadata.uid = uid; - Actions.setMetadata(draft, PLUGIN_ID, metadata); } } } diff --git a/spec/tasks/send-draft-spec.coffee b/spec/tasks/send-draft-spec.coffee index 578b7b6ac..9753d3fd2 100644 --- a/spec/tasks/send-draft-spec.coffee +++ b/spec/tasks/send-draft-spec.coffee @@ -115,7 +115,8 @@ describe "SendDraftTask", -> waitsForPromise => @task.performRemote().then => args = Actions.sendDraftSuccess.calls[0].args[0] - expect(args.draftClientId).toBe @draft.clientId + expect(args.message instanceof Message).toBe(true) + expect(args.messageClientId).toBe(@draft.clientId) it "should play a sound", -> spyOn(NylasEnv.config, "get").andReturn true diff --git a/src/components/contenteditable/extended-selection.coffee b/src/components/contenteditable/extended-selection.coffee index 5e8af4cc9..2ea0c1039 100644 --- a/src/components/contenteditable/extended-selection.coffee +++ b/src/components/contenteditable/extended-selection.coffee @@ -52,6 +52,7 @@ class ExtendedSelection selectFromTo: (from, to) -> fromNode = @findNodeAt(from) toNode = @findNodeAt(to) + return unless fromNode and toNode @setBaseAndExtent(fromNode, 0, toNode, (toNode.length ? 0)) selectFromToWithIndex: (from, fromIndex, to, toIndex) -> @@ -59,6 +60,7 @@ class ExtendedSelection toNode = @findNodeAt(to) if (not _.isNumber(fromIndex)) or (not _.isNumber(toIndex)) throw @_errBadUsage() + return unless fromNode and toNode @setBaseAndExtent(fromNode, fromIndex, toNode, toIndex) exportSelection: -> new ExportedSelection(@rawSelection, @scopeNode) diff --git a/src/components/metadata-composer-toggle-button.jsx b/src/components/metadata-composer-toggle-button.jsx index 021aaf93a..76d922dce 100644 --- a/src/components/metadata-composer-toggle-button.jsx +++ b/src/components/metadata-composer-toggle-button.jsx @@ -12,7 +12,7 @@ export default class MetadataComposerToggleButton extends React.Component { iconName: React.PropTypes.string, pluginId: React.PropTypes.string.isRequired, pluginName: React.PropTypes.string.isRequired, - metadataKey: React.PropTypes.string.isRequired, + metadataEnabledValue: React.PropTypes.object.isRequired, stickyToggle: React.PropTypes.bool, errorMessage: React.PropTypes.func.isRequired, draftClientId: React.PropTypes.string.isRequired, @@ -55,21 +55,19 @@ export default class MetadataComposerToggleButton extends React.Component { if (!metadata) { if (!this.state.isSetup) { if (this._isDefaultOn()) { - this._setMetadataValueTo(true) + this._setEnabled(true) } this.setState({isSetup: true}) } } else { - this.setState({enabled: metadata.tracked, isSetup: true}); + this.setState({enabled: true, isSetup: true}); } }; - _setMetadataValueTo(enabled) { - const newValue = {} - newValue[this.props.metadataKey] = enabled + _setEnabled(enabled) { + const metadataValue = enabled ? this.props.metadataEnabledValue : null; 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(); @@ -108,7 +106,7 @@ export default class MetadataComposerToggleButton extends React.Component { if (this.props.stickyToggle) { NylasEnv.config.set(this._configKey(), !this.state.enabled) } - this._setMetadataValueTo(!this.state.enabled) + this._setEnabled(!this.state.enabled) }; render() { diff --git a/src/flux/tasks/send-draft.coffee b/src/flux/tasks/send-draft.coffee index 2f3def9a6..32871901c 100644 --- a/src/flux/tasks/send-draft.coffee +++ b/src/flux/tasks/send-draft.coffee @@ -199,7 +199,7 @@ class SendDraftTask extends Task Actions.queueTask(task) ) - Actions.sendDraftSuccess draftClientId: @message.clientId + Actions.sendDraftSuccess(message: @message, messageClientId: @message.clientId) # Play the sending sound if NylasEnv.config.get("core.sending.sounds")