es6(*): Misc components, nylas-exports => ES2016

This commit is contained in:
Ben Gotow 2016-10-27 18:25:30 -07:00
parent 0a56d30a22
commit a9b0472030
23 changed files with 754 additions and 854 deletions

View file

@ -63,7 +63,8 @@ module.exports = (grunt) ->
content = fs.readFileSync(f, encoding:'utf8')
if /module.exports\s?=\s?.+/gmi.test(content)
errors.push("#{f}: Don't use module.exports in ES6")
unless f.endsWith('nylas-exports.es6')
errors.push("#{f}: Don't use module.exports in ES6")
if /^export/gmi.test(content)
if /^export\ default/gmi.test(content)

View file

@ -3,7 +3,7 @@ Actions = require '../src/flux/actions'
Message = require('../src/flux/models/message').default
DatabaseStore = require('../src/flux/stores/database-store').default
AccountStore = require '../src/flux/stores/account-store'
ActionBridge = require '../src/flux/action-bridge',
ActionBridge = require('../src/flux/action-bridge').default,
_ = require 'underscore'
ipc =

View file

@ -1,46 +0,0 @@
React = require 'react'
_ = require 'underscore'
{Actions, ComponentRegistry, Utils} = require "nylas-exports"
###
Public: A simple wrapper that provides a Flexbox layout with the given direction and style.
Any additional props you set on the Flexbox are rendered.
Section: Component Kit
###
class Flexbox extends React.Component
@displayName: 'Flexbox'
###
Public: React `props` supported by Flexbox:
- `direction` (optional) A {String} Flexbox direction: either `column` or `row`.
- `style` (optional) An {Object} with styles to apply to the flexbox.
###
@propTypes:
direction: React.PropTypes.string
inline: React.PropTypes.bool
style: React.PropTypes.object
height: React.PropTypes.string
@defaultProps:
height: '100%'
render: ->
style = _.extend {},
'flexDirection': @props.direction,
'position':'relative'
'display': 'flex'
'height': @props.height
, (@props.style || {})
if @props.inline is true
style.display = 'inline-flex'
otherProps = Utils.fastOmit(@props, Object.keys(@constructor.propTypes))
<div style={style} {...otherProps}>
{@props.children}
</div>
module.exports = Flexbox

View file

@ -0,0 +1,51 @@
import React from 'react';
import {Utils} from "nylas-exports";
/**
Public: A simple wrapper that provides a Flexbox layout with the given direction and style.
Any additional props you set on the Flexbox are rendered.
Section: Component Kit
*/
export default class Flexbox extends React.Component {
static displayName = 'Flexbox';
/**
Public: React `props` supported by Flexbox:
- `direction` (optional) A {String} Flexbox direction: either `column` or `row`.
- `style` (optional) An {Object} with styles to apply to the flexbox.
*/
static propTypes = {
direction: React.PropTypes.string,
inline: React.PropTypes.bool,
style: React.PropTypes.object,
height: React.PropTypes.string,
};
static defaultProps = {
height: '100%',
style: {},
};
render() {
const style = Object.assign({}, {
flexDirection: this.props.direction,
position: 'relative',
display: 'flex',
height: this.props.height,
}, this.props.style);
if (this.props.inline === true) {
style.display = 'inline-flex';
}
const otherProps = Utils.fastOmit(this.props, Object.keys(this.constructor.propTypes));
return (
<div style={style} {...otherProps}>
{this.props.children}
</div>
);
}
}

View file

@ -1,35 +0,0 @@
React = require 'react'
_ = require 'underscore'
class FluxContainer extends React.Component
@displayName: 'FluxContainer'
@propTypes:
children: React.PropTypes.element
stores: React.PropTypes.array.isRequired
getStateFromStores: React.PropTypes.func.isRequired
constructor: (@props) ->
@_unlisteners = []
@state = @props.getStateFromStores()
componentDidMount: ->
@setupListeners()
componentWillReceiveProps: (nextProps) ->
@setState(nextProps.getStateFromStores())
@setupListeners(nextProps)
setupListeners: (props = @props) ->
unlisten() for unlisten in @_unlisteners
@_unlisteners = props.stores.map (store) =>
store.listen => @setState(props.getStateFromStores())
componentWillUnmount: ->
unlisten() for unlisten in @_unlisteners
@_unlisteners = []
render: ->
otherProps = _.omit(@props, Object.keys(@constructor.propTypes))
React.cloneElement(@props.children, _.extend({}, otherProps, @state))
module.exports = FluxContainer

View file

@ -0,0 +1,52 @@
import React from 'react';
import {Utils} from 'nylas-exports';
class FluxContainer extends React.Component {
static displayName = 'FluxContainer';
static propTypes = {
children: React.PropTypes.element,
stores: React.PropTypes.array.isRequired,
getStateFromStores: React.PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this._unlisteners = [];
this.state = this.props.getStateFromStores();
}
componentDidMount() {
return this.setupListeners();
}
componentWillReceiveProps(nextProps) {
this.setState(nextProps.getStateFromStores());
return this.setupListeners(nextProps);
}
componentWillUnmount() {
for (const unlisten of this._unlisteners) {
unlisten();
}
this._unlisteners = [];
}
setupListeners(props = this.props) {
for (const unlisten of this._unlisteners) {
unlisten();
}
this._unlisteners = props.stores.map((store) => {
return store.listen(() =>
this.setState(props.getStateFromStores())
);
});
}
render() {
const otherProps = Utils.fastOmit(this.props, Object.keys(this.constructor.propTypes));
return React.cloneElement(this.props.children, Object.assign({}, otherProps, this.state));
}
}
export default FluxContainer;

View file

@ -1,7 +1,7 @@
React = require 'react'
_ = require 'underscore'
UnsafeComponent = require './unsafe-component'
Flexbox = require './flexbox'
Flexbox = require('./flexbox').default
InjectedComponentLabel = require('./injected-component-label').default
{Utils,
Actions,

View file

@ -1,51 +0,0 @@
React = require 'react'
RetinaImg = require('./retina-img').default
CategoryStore = require '../flux/stores/category-store'
LabelColorizer =
color: (label) -> "hsl(#{label.hue()}, 50%, 34%)"
backgroundColor: (label) -> "hsl(#{label.hue()}, 62%, 87%)"
backgroundColorDark: (label) -> "hsl(#{label.hue()}, 62%, 57%)"
styles: (label) ->
styles =
color: LabelColorizer.color(label)
backgroundColor: LabelColorizer.backgroundColor(label)
boxShadow: "inset 0 0 1px hsl(#{label.hue()}, 62%, 47%), inset 0 1px 1px rgba(255,255,255,0.5), 0 0.5px 0 rgba(255,255,255,0.5)"
if process.platform isnt "win32"
styles["backgroundImage"] = 'linear-gradient(rgba(255,255,255, 0.4), rgba(255,255,255,0))'
return styles
class MailLabel extends React.Component
@propTypes:
label: React.PropTypes.object.isRequired
onRemove: React.PropTypes.func
shouldComponentUpdate: (nextProps, nextState) ->
return false if nextProps.label.id is @props.label.id
true
render: ->
classname = 'mail-label'
content = @props.label.displayName
x = null
if @_removable()
classname += ' removable'
content = <span className="inner">{content}</span>
x = <RetinaImg
className="x"
name="label-x.png"
style={backgroundColor: LabelColorizer.color(@props.label)}
mode={RetinaImg.Mode.ContentIsMask}
onClick={@props.onRemove}/>
<div className={classname} style={LabelColorizer.styles(@props.label)}>{content}{x}</div>
_removable: ->
isLockedLabel = @props.label.isLockedCategory()
return @props.onRemove and not isLockedLabel
module.exports = {MailLabel, LabelColorizer}

View file

@ -0,0 +1,75 @@
import React from 'react';
import RetinaImg from './retina-img';
const LabelColorizer = {
color(label) {
return `hsl(${label.hue()}, 50%, 34%)`;
},
backgroundColor(label) {
return `hsl(${label.hue()}, 62%, 87%)`;
},
backgroundColorDark(label) {
return `hsl(${label.hue()}, 62%, 57%)`;
},
styles(label) {
const styles = {
color: LabelColorizer.color(label),
backgroundColor: LabelColorizer.backgroundColor(label),
boxShadow: `inset 0 0 1px hsl(${label.hue()}, 62%, 47%), inset 0 1px 1px rgba(255,255,255,0.5), 0 0.5px 0 rgba(255,255,255,0.5)`,
};
if (process.platform !== "win32") {
styles.backgroundImage = 'linear-gradient(rgba(255,255,255, 0.4), rgba(255,255,255,0))';
}
return styles;
},
};
class MailLabel extends React.Component {
static propTypes = {
label: React.PropTypes.object.isRequired,
onRemove: React.PropTypes.func,
};
shouldComponentUpdate(nextProps) {
if (nextProps.label.id === this.props.label.id) { return false; }
return true;
}
_removable() {
return this.props.onRemove && !this.props.label.isLockedCategory();
}
render() {
let classname = 'mail-label'
let content = this.props.label.displayName
let x = null;
if (this._removable()) {
classname += ' removable'
content = <span className="inner">{content}</span>
x = (
<RetinaImg
className="x"
name="label-x.png"
style={{backgroundColor: LabelColorizer.color(this.props.label)}}
mode={RetinaImg.Mode.ContentIsMask}
onClick={this.props.onRemove}
/>
);
}
return (
<div
className={classname}
style={LabelColorizer.styles(this.props.label)}
>
{content}{x}
</div>
);
}
}
export { MailLabel, LabelColorizer };

View file

@ -4,8 +4,7 @@ _ = require 'underscore'
{Utils, Actions} = require "nylas-exports"
InjectedComponentSet = require './injected-component-set'
RetinaImg = requir(('./retina-img').default).default
Flexbox = require './flexbox'
Flexbox = require('./flexbox').default
###
Public: MultiselectActionBar is a simple component that can be placed in a {Sheet} Toolbar.

View file

@ -1,86 +0,0 @@
_ = require 'underscore'
request = require 'request'
React = require 'react'
{Utils, EdgehillAPI} = require "nylas-exports"
{RetinaImg, Flexbox} = require 'nylas-component-kit'
class NewsletterSignup extends React.Component
@displayName: 'NewsletterSignup'
@propTypes:
name: React.PropTypes.string
emailAddress: React.PropTypes.string
constructor: (@props) ->
@state = {status: 'Pending'}
componentWillReceiveProps: (nextProps) =>
@_onGetStatus(nextProps) if not _.isEqual(@props, nextProps)
componentDidMount: =>
@_mounted = true
@_onGetStatus()
componentWillUnmount: =>
@_mounted = false
_setState: (state) =>
return unless @_mounted
@setState(state)
_onGetStatus: (props = @props) =>
@_setState({status: 'Pending'})
EdgehillAPI.makeRequest
method: 'GET'
path: @_path(props)
success: (status) =>
if status is 'Never Subscribed'
@_onSubscribe()
else
@_setState({status})
error: =>
@_setState({status: "Error"})
_onSubscribe: =>
@_setState({status: 'Pending'})
EdgehillAPI.makeRequest
method: 'POST'
path: @_path()
success: (status) =>
@_setState({status})
error: =>
@_setState({status: "Error"})
_onUnsubscribe: =>
@_setState({status: 'Pending'})
EdgehillAPI.makeRequest
method: 'DELETE'
path: @_path()
success: (status) =>
@_setState({status})
error: =>
@_setState({status: "Error"})
_path: (props = @props) =>
"/newsletter-subscription/#{encodeURIComponent(props.emailAddress)}?name=#{encodeURIComponent(props.name)}"
render: =>
<Flexbox direction='row' height='auto' style={textAlign: 'left'}>
<div style={minWidth:15}>
{@_renderControl()}
</div>
<label htmlFor="subscribe-check" style={paddingLeft: 4, flex: 1}>
Notify me about new features and plugins via this email address.
</label>
</Flexbox>
_renderControl: ->
if @state.status is 'Pending'
<RetinaImg name='inline-loading-spinner.gif' mode={RetinaImg.Mode.ContentDark} style={width:14, height:14}/>
else if @state.status is 'Error'
<button onClick={@_onGetStatus} className="btn">Retry</button>
else if @state.status in ['Subscribed', 'Active']
<input id="subscribe-check" type="checkbox" checked={true} style={marginTop:3} onChange={@_onUnsubscribe} />
else
<input id="subscribe-check" type="checkbox" checked={false} style={marginTop:3} onChange={@_onSubscribe} />
module.exports = NewsletterSignup

View file

@ -0,0 +1,113 @@
import _ from 'underscore';
import React from 'react';
import {EdgehillAPI} from "nylas-exports";
import {RetinaImg, Flexbox} from 'nylas-component-kit';
export default class NewsletterSignup extends React.Component {
static displayName = 'NewsletterSignup';
static propTypes = {
name: React.PropTypes.string,
emailAddress: React.PropTypes.string,
};
constructor(props) {
super(props);
this.state = {status: 'Pending'};
}
componentDidMount() {
this._mounted = true;
return this._onGetStatus();
}
componentWillReceiveProps(nextProps) {
if (!_.isEqual(this.props, nextProps)) {
this._onGetStatus(nextProps);
}
}
componentWillUnmount() {
this._mounted = false;
}
_setState(state) {
if (!this._mounted) { return; }
this.setState(state);
}
_onGetStatus(props = this.props) {
this._setState({status: 'Pending'});
EdgehillAPI.makeRequest({
method: 'GET',
path: this._path(props),
success: status => {
if (status === 'Never Subscribed') {
this._onSubscribe();
} else {
this._setState({status});
}
},
error: () => {
this._setState({status: "Error"});
},
});
}
_onSubscribe() {
this._setState({status: 'Pending'});
EdgehillAPI.makeRequest({
method: 'POST',
path: this._path(),
success: status => {
this._setState({status});
},
error: () => {
this._setState({status: "Error"});
},
});
}
_onUnsubscribe() {
this._setState({status: 'Pending'});
EdgehillAPI.makeRequest({
method: 'DELETE',
path: this._path(),
success: status => {
this._setState({status});
},
error: () => {
this._setState({status: "Error"});
},
});
}
_path(props = this.props) {
return `/newsletter-subscription/${encodeURIComponent(props.emailAddress)}?name=${encodeURIComponent(props.name)}`;
}
_renderControl() {
if (this.state.status === 'Pending') {
return (<RetinaImg name="inline-loading-spinner.gif" mode={RetinaImg.Mode.ContentDark} style={{width: 14, height: 14}} />);
}
if (this.state.status === 'Error') {
return (<button onClick={this._onGetStatus} className="btn">Retry</button>);
}
if (['Subscribed', 'Active'].includes(this.state.status)) {
return (<input id="subscribe-check" type="checkbox" checked style={{marginTop: 3}} onChange={this._onUnsubscribe} />);
}
return (<input id="subscribe-check" type="checkbox" checked={false} style={{marginTop: 3}} onChange={this._onSubscribe} />);
}
render() {
return (
<Flexbox direction="row" height="auto" style={{textAlign: 'left'}}>
<div style={{minWidth: 15}}>
{this._renderControl()}
</div>
<label htmlFor="subscribe-check" style={{paddingLeft: 4, flex: 1}}>
Notify me about new features and plugins via this email address.
</label>
</Flexbox>
);
}
}

View file

@ -1,147 +0,0 @@
_ = require 'underscore'
Actions = require './actions'
Model = require './models/model'
DatabaseStore = require('./stores/database-store').default
DatabaseChangeRecord = require('./stores/database-change-record').default
Utils = require './models/utils'
Role =
WORK: 'work',
SECONDARY: 'secondary'
TargetWindows =
ALL: 'all',
WORK: 'work'
Message =
DATABASE_STORE_TRIGGER: 'db-store-trigger'
printToConsole = false
# Public: ActionBridge
#
# The ActionBridge has two responsibilities:
# 1. When you're in a secondary window, the ActionBridge observes all Root actions. When a
# Root action is fired, it converts it's payload to JSON, tunnels it to the main window
# via IPC, and re-fires the Action in the main window. This means that calls to actions
# like Actions.queueTask(task) can be fired in secondary windows and consumed by the
# TaskQueue, which only lives in the main window.
# 2. The ActionBridge listens to the DatabaseStore and re-broadcasts it's trigger() event
# into all of the windows of the application. This is important, because the DatabaseStore
# in all secondary windows is a read-replica. Only the DatabaseStore in the main window
# of the application consumes persistModel actions and writes changes to the database.
class ActionBridge
@Role: Role
@Message: Message
@TargetWindows: TargetWindows
constructor: (ipc) ->
@globalActions = []
@ipc = ipc
@ipcLastSendTime = null
@initiatorId = NylasEnv.getWindowType()
@role = if NylasEnv.isWorkWindow() then Role.WORK else Role.SECONDARY
NylasEnv.onBeforeUnload(@onBeforeUnload)
# Listen for action bridge messages from other windows
@ipc.on('action-bridge-message', @onIPCMessage)
# Observe all global actions and re-broadcast them to other windows
Actions.globalActions.forEach (name) =>
callback = (args...) => @onRebroadcast(TargetWindows.ALL, name, args)
Actions[name].listen(callback, @)
# Observe the database store (possibly other stores in the future), and
# rebroadcast it's trigger() event.
databaseCallback = (change) =>
return if DatabaseStore.triggeringFromActionBridge
@onRebroadcast(TargetWindows.ALL, Message.DATABASE_STORE_TRIGGER, [change])
DatabaseStore.listen(databaseCallback, @)
if @role isnt Role.WORK
# Observe all mainWindow actions fired in this window and re-broadcast
# them to other windows so the central application stores can take action
Actions.workWindowActions.forEach (name) =>
callback = (args...) => @onRebroadcast(TargetWindows.WORK, name, args)
Actions[name].listen(callback, @)
registerGlobalActions: ({pluginName, actions}) =>
_.each actions, (actionFn, name) =>
@globalActions.push({name, actionFn, scope: pluginName})
callback = (args...) =>
broadcastName = "#{pluginName}::#{name}"
@onRebroadcast(TargetWindows.ALL, broadcastName, args)
actionFn.listen(callback, @)
_isExtensionAction: (name) ->
name.split("::").length is 2
_globalExtensionAction: (broadcastName) ->
[scope, name] = broadcastName.split("::")
{actionFn} = _.findWhere(@globalActions, {scope, name}) ? {}
return actionFn
onIPCMessage: (event, initiatorId, name, json) =>
# There's something very strange about IPC event handlers. The ReactRemoteParent
# threw React exceptions when calling setState from an IPC callback, and the debugger
# often refuses to stop at breakpoints immediately inside IPC callbacks.
# These issues go away when you add a process.nextTick. So here's that.
# I believe this resolves issues like https://sentry.nylas.com/sentry/edgehill/group/2735/,
# which are React exceptions in a direct stack (no next ticks) from an IPC event.
process.nextTick =>
console.debug(printToConsole, "ActionBridge: #{@initiatorId} Action Bridge Received: #{name}")
args = JSON.parse(json, Utils.registeredObjectReviver)
if name == Message.DATABASE_STORE_TRIGGER
DatabaseStore.triggeringFromActionBridge = true
DatabaseStore.trigger(new DatabaseChangeRecord(args[0]))
DatabaseStore.triggeringFromActionBridge = false
else if Actions[name]
Actions[name].firing = true
Actions[name](args...)
else if @_isExtensionAction(name)
fn = @_globalExtensionAction(name)
if fn
fn.firing = true
fn(args...)
else
throw new Error("#{@initiatorId} received unknown action-bridge event: #{name}")
onRebroadcast: (target, name, args) =>
if Actions[name]?.firing
Actions[name].firing = false
return
else if @_globalExtensionAction(name)?.firing
@_globalExtensionAction(name).firing = false
return
params = []
args.forEach (arg) ->
if arg instanceof Function
throw new Error("ActionBridge cannot forward action argument of type `function` to work window.")
params.push(arg)
json = JSON.stringify(params, Utils.registeredObjectReplacer)
console.debug(printToConsole, "ActionBridge: #{@initiatorId} Action Bridge Broadcasting: #{name}")
@ipc.send("action-bridge-rebroadcast-to-#{target}", @initiatorId, name, json)
@ipcLastSendTime = Date.now()
onBeforeUnload: (readyToUnload) =>
# Unfortunately, if you call ipc.send and then immediately close the window,
# Electron won't actually send the message. To work around this, we wait an
# arbitrary amount of time before closing the window after the last IPC event
# was sent. https://github.com/atom/electron/issues/4366
if @ipcLastSendTime and Date.now() - @ipcLastSendTime < 100
setTimeout(readyToUnload, 100)
return false
return true
module.exports = ActionBridge

176
src/flux/action-bridge.es6 Normal file
View file

@ -0,0 +1,176 @@
import _ from 'underscore';
import Actions from './actions';
import DatabaseStore from './stores/database-store';
import DatabaseChangeRecord from './stores/database-change-record';
import Utils from './models/utils';
const Role = {
WORK: 'work',
SECONDARY: 'secondary',
};
const TargetWindows = {
ALL: 'all',
WORK: 'work',
};
const Message = {
DATABASE_STORE_TRIGGER: 'db-store-trigger',
};
const printToConsole = false;
// Public: ActionBridge
//
// The ActionBridge has two responsibilities:
// 1. When you're in a secondary window, the ActionBridge observes all Root actions. When a
// Root action is fired, it converts it's payload to JSON, tunnels it to the main window
// via IPC, and re-fires the Action in the main window. This means that calls to actions
// like Actions.queueTask(task) can be fired in secondary windows and consumed by the
// TaskQueue, which only lives in the main window.
// 2. The ActionBridge listens to the DatabaseStore and re-broadcasts it's trigger() event
// into all of the windows of the application. This is important, because the DatabaseStore
// in all secondary windows is a read-replica. Only the DatabaseStore in the main window
// of the application consumes persistModel actions and writes changes to the database.
class ActionBridge {
static Role = Role;
static Message = Message;
static TargetWindows = TargetWindows;
constructor(ipc) {
this.registerGlobalActions = this.registerGlobalActions.bind(this);
this.onIPCMessage = this.onIPCMessage.bind(this);
this.onRebroadcast = this.onRebroadcast.bind(this);
this.onBeforeUnload = this.onBeforeUnload.bind(this);
this.globalActions = [];
this.ipc = ipc;
this.ipcLastSendTime = null;
this.initiatorId = NylasEnv.getWindowType();
this.role = NylasEnv.isWorkWindow() ? Role.WORK : Role.SECONDARY;
NylasEnv.onBeforeUnload(this.onBeforeUnload);
// Listen for action bridge messages from other windows
this.ipc.on('action-bridge-message', this.onIPCMessage);
// Observe all global actions and re-broadcast them to other windows
Actions.globalActions.forEach(name => {
const callback = (...args) => this.onRebroadcast(TargetWindows.ALL, name, args);
return Actions[name].listen(callback, this);
});
// Observe the database store (possibly other stores in the future), and
// rebroadcast it's trigger() event.
const databaseCallback = change => {
if (DatabaseStore.triggeringFromActionBridge) { return; }
this.onRebroadcast(TargetWindows.ALL, Message.DATABASE_STORE_TRIGGER, [change]);
};
DatabaseStore.listen(databaseCallback, this);
if (this.role !== Role.WORK) {
// Observe all mainWindow actions fired in this window and re-broadcast
// them to other windows so the central application stores can take action
Actions.workWindowActions.forEach(name => {
const callback = (...args) => this.onRebroadcast(TargetWindows.WORK, name, args);
return Actions[name].listen(callback, this);
});
}
}
registerGlobalActions({pluginName, actions}) {
return _.each(actions, (actionFn, name) => {
this.globalActions.push({name, actionFn, scope: pluginName});
const callback = (...args) => {
const broadcastName = `${pluginName}::${name}`;
return this.onRebroadcast(TargetWindows.ALL, broadcastName, args);
};
return actionFn.listen(callback, this);
}
);
}
_isExtensionAction(name) {
return name.split("::").length === 2;
}
_globalExtensionAction(broadcastName) {
const [scope, name] = broadcastName.split("::");
return (_.findWhere(this.globalActions, {scope, name}) || {}).actionFn
}
onIPCMessage(event, initiatorId, name, json) {
// There's something very strange about IPC event handlers. The ReactRemoteParent
// threw React exceptions when calling setState from an IPC callback, and the debugger
// often refuses to stop at breakpoints immediately inside IPC callbacks.
// These issues go away when you add a process.nextTick. So here's that.
// I believe this resolves issues like https://sentry.nylas.com/sentry/edgehill/group/2735/,
// which are React exceptions in a direct stack (no next ticks) from an IPC event.
process.nextTick(() => {
console.debug(printToConsole, `ActionBridge: ${this.initiatorId} Action Bridge Received: ${name}`);
const args = JSON.parse(json, Utils.registeredObjectReviver);
if (name === Message.DATABASE_STORE_TRIGGER) {
DatabaseStore.triggeringFromActionBridge = true;
DatabaseStore.trigger(new DatabaseChangeRecord(args[0]));
DatabaseStore.triggeringFromActionBridge = false;
} else if (Actions[name]) {
Actions[name].firing = true;
Actions[name](...args);
} else if (this._isExtensionAction(name)) {
const fn = this._globalExtensionAction(name);
if (fn) {
fn.firing = true;
fn(...args);
}
} else {
throw new Error(`${this.initiatorId} received unknown action-bridge event: ${name}`);
}
});
}
onRebroadcast(target, name, args) {
if (Actions[name] && Actions[name].firing) {
Actions[name].firing = false;
return;
}
const globalExtAction = this._globalExtensionAction(name);
if (globalExtAction && globalExtAction.firing) {
globalExtAction.firing = false;
return;
}
const params = [];
args.forEach((arg) => {
if (arg instanceof Function) {
throw new Error("ActionBridge cannot forward action argument of type `function` to work window.");
}
return params.push(arg);
});
const json = JSON.stringify(params, Utils.registeredObjectReplacer);
console.debug(printToConsole, `ActionBridge: ${this.initiatorId} Action Bridge Broadcasting: ${name}`);
this.ipc.send(`action-bridge-rebroadcast-to-${target}`, this.initiatorId, name, json);
this.ipcLastSendTime = Date.now();
}
onBeforeUnload(readyToUnload) {
// Unfortunately, if you call ipc.send and then immediately close the window,
// Electron won't actually send the message. To work around this, we wait an
// arbitrary amount of time before closing the window after the last IPC event
// was sent. https://github.com/atom/electron/issues/4366
if (this.ipcLastSendTime && Date.now() - this.ipcLastSendTime < 100) {
setTimeout(readyToUnload, 100);
return false;
}
return true;
}
}
export default ActionBridge;

View file

@ -1,29 +0,0 @@
# This file contains custom Nylas error classes.
#
# In general I think these should be created as sparingly as possible.
# Only add one if you really can't use native `new Error("my msg")`
# A wrapper around the three arguments we get back from node's `request`
# method. We wrap it in an error object because Promises can only call
# `reject` or `resolve` with one argument (not three).
class APIError extends Error
constructor: ({@error, @response, @body, @requestOptions, @statusCode} = {}) ->
@statusCode ?= @response?.statusCode
@requestOptions ?= @response?.requestOptions
@name = "APIError"
@stack = (new Error()).stack
@message = @body?.message ? @body ? @error?.toString?()
@errorTitle = @body?.error
fromJSON: (json = {}) ->
for key, val of json
@[key] = val
@
class TimeoutError extends Error
constructor: ->
module.exports =
"APIError": APIError
"TimeoutError": TimeoutError

43
src/flux/errors.es6 Normal file
View file

@ -0,0 +1,43 @@
// This file contains custom Nylas error classes.
//
// In general I think these should be created as sparingly as possible.
// Only add one if you really can't use native `new Error("my msg")`
// A wrapper around the three arguments we get back from node's `request`
// method. We wrap it in an error object because Promises can only call
// `reject` or `resolve` with one argument (not three).
export class APIError extends Error {
constructor({error, response, body, requestOptions, statusCode} = {}) {
super();
this.name = "APIError";
this.error = error;
this.response = response;
this.body = body;
this.requestOptions = requestOptions;
this.statusCode = statusCode;
if (this.statusCode == null) {
this.statusCode = this.response ? this.response.statusCode : null;
}
if (this.requestOptions == null) {
this.requestOptions = this.response ? this.response.requestOptions : null;
}
this.stack = (new Error()).stack;
this.message = (this.body ? this.body.message : null) || this.body || (this.error ? this.error.toString() : null);
this.errorTitle = (this.body ? this.body.error : null);
}
fromJSON(json = {}) {
for (const key of Object.keys(json)) {
this[key] = json[key];
}
return this;
}
}
export class TimeoutError extends Error {
}

View file

@ -1,230 +0,0 @@
Model = require './model'
Utils = require './utils'
Attributes = require('../attributes').default
RegExpUtils = require '../../regexp-utils'
AccountStore = require '../stores/account-store'
FocusedPerspectiveStore = null # Circular Dependency
_str = require 'underscore.string'
_ = require 'underscore'
name_prefixes = {}
name_suffixes = {}
###
Public: The Contact model represents a Contact object served by the Nylas Platform API.
For more information about Contacts on the Nylas Platform, read the
[Contacts API Documentation](https://nylas.com/cloud/docs#contacts)
## Attributes
`name`: {AttributeString} The name of the contact. Queryable.
`email`: {AttributeString} The email address of the contact. Queryable.
`thirdPartyData`: {AttributeObject} Extra data that we find out about a
contact. The data is keyed by the 3rd party service that dumped the data
there. The value is an object of raw data in the form that the service
provides
We also have "normalized" optional data for each contact. This list may
grow as the needs of a contact become more complex.
This class also inherits attributes from {Model}
Section: Models
###
class Contact extends Model
constructor: ->
@thirdPartyData ?= {}
super
@attributes: _.extend {}, Model.attributes,
'name': Attributes.String
queryable: true
modelKey: 'name'
'email': Attributes.String
queryable: true
modelKey: 'email'
# Contains the raw thirdPartyData (keyed by the vendor name) about
# this contact.
'thirdPartyData': Attributes.Object
modelKey: 'thirdPartyData'
# The following are "normalized" fields that we can use to consolidate
# various thirdPartyData source. These list of attributes should
# always be optional and may change as the needs of a Nylas contact
# change over time.
'title': Attributes.String(modelKey: 'title')
'phone': Attributes.String(modelKey: 'phone')
'company': Attributes.String(modelKey: 'company')
@additionalSQLiteConfig:
setup: ->
['CREATE INDEX IF NOT EXISTS ContactEmailIndex ON Contact(email)']
@fromString: (string, {accountId} = {}) ->
emailRegex = RegExpUtils.emailRegex()
match = emailRegex.exec(string)
if emailRegex.exec(string)
throw new Error('Error while calling Contact.fromString: string contains more than one email')
email = match[0]
name = string[0...match.index - 1]
name = name[0...-1] if name[name.length - 1] in ['<', '(']
name = name.trim()
return new Contact
accountId: accountId
name: name
email: email
# Public: Returns a string of the format `Full Name <email@address.com>` if
# the contact has a populated name, just the email address otherwise.
toString: ->
# Note: This is used as the drag-drop text of a Contact token, in the
# creation of message bylines "From Ben Gotow <ben@nylas>", and several other
# places. Change with care.
if @name and @name isnt @email then "#{@name} <#{@email}>" else @email
toJSON: ->
json = super
json['name'] ||= json['email']
json
# Public: Returns true if the contact provided is a {Contact} instance and
# contains a properly formatted email address.
#
isValid: ->
return false unless @email
# The email regexp must match the /entire/ email address
result = RegExpUtils.emailRegex().exec(@email)
if result and result instanceof Array
return result[0] is @email
else return false
@email.match(RegExpUtils.emailRegex()) != null
# Public: Returns true if the contact is the current user, false otherwise.
# You should use this method instead of comparing the user's email address to
# the account email, since it is case-insensitive and future-proof.
isMe: ->
account = AccountStore.accountForEmail(@email)
return account?
hasSameDomainAsMe: ->
for myEmail in AccountStore.emailAddresses()
return true if Utils.emailsHaveSameDomain(@email, myEmail)
return false
isMePhrase: ({includeAccountLabel, forceAccountLabel} = {}) ->
account = AccountStore.accountForEmail(@email)
return null unless account
if includeAccountLabel
FocusedPerspectiveStore ?= require('../stores/focused-perspective-store').default
if account and (FocusedPerspectiveStore.current().accountIds.length > 1 || forceAccountLabel)
return "You (#{account.label})"
return "You"
# Returns a {String} display name.
# - "You" if the contact is the current user or an alias for the current user.
# - `name` if the contact has a populated name
# - `email` in all other cases.
#
# You can pass several options to customize the name:
# - includeAccountLabel: If the contact represents the current user, include
# the account label afer "You"
# - compact: If the contact has a name, make the name as short as possible
# (generally returns just the first name.)
#
displayName: ({includeAccountLabel, compact, forceAccountLabel} = {}) ->
compact ?= false
includeAccountLabel ?= !compact
if compact
@isMePhrase({includeAccountLabel, forceAccountLabel}) ? @firstName()
else
@isMePhrase({includeAccountLabel, forceAccountLabel}) ? @fullName()
fullName: ->
return @_nameParts().join(' ')
firstName: ->
exclusions = ['a', 'the', 'dr.', 'mrs.', 'mr.', 'mx.', 'prof.', 'ph.d.']
for part in @_nameParts()
if part.toLowerCase() not in exclusions
return part
return ""
lastName: ->
@_nameParts()[1..-1]?.join(" ") ? ""
nameAbbreviation: ->
c1 = @firstName()[0]?.toUpperCase() ? ""
c2 = @lastName()[0]?.toUpperCase() ? ""
return c1+c2
guessCompanyFromEmail: (email = @email) ->
return "" if (Utils.emailHasCommonDomain(email))
domain = _.last(email.toLowerCase().trim().split("@"))
domainParts = domain.split(".")
if (domainParts.length >= 2)
return _str.titleize(_str.humanize(domainParts[domainParts.length - 2]))
return ""
_nameParts: ->
name = @name
# At this point, if the name is empty we'll use the email address
unless name && name.length
name = (@email || "")
# If the phrase has an '@', use everything before the @ sign
# Unless there that would result in an empty string.
name = name.split('@')[0] if name.indexOf('@') > 0
# Take care of phrases like "Mike Kaylor via LinkedIn" that should be displayed
# as the contents before the separator word. Do not break "Olivia"
name = name.split(/(\svia\s)/i)[0]
# Take care of whitespace
name = name.trim()
# Handle last name, first name
parts = @_parseReverseNames(name)
# Split the name into words and remove parts that are prefixes and suffixes
if parts.join('').length == 0
parts = []
parts = name.split(/\s+/)
parts = _.reject parts, (part) ->
part = part.toLowerCase().replace(/\./,'')
(part of name_prefixes) or (part of name_suffixes)
# If we've removed all the parts, just return the whole name
parts = [name] if parts.join('').length == 0
# If all that failed, fall back to email
parts = [@email] if parts.join('').length == 0
parts
_parseReverseNames: (name) ->
parts = []
[lastName, firstName] = name.split(',')
if firstName
[firstName, description] = firstName.split('(')
parts.push(firstName.trim())
parts.push(lastName.trim())
parts.push("(" + description.trim()) if description
parts
module.exports = Contact
_.each ['2dlt','2lt','2nd lieutenant','adm','administrative','admiral','amb','ambassador','attorney','atty','baron','baroness','bishop','br','brig gen or bg','brigadier general','brnss','brother','capt','captain','chancellor','chaplain','chapln','chief petty officer','cmdr','cntss','coach','col','colonel','commander','corporal','count','countess','cpl','cpo','cpt','doctor','dr','dr and mrs','drs','duke','ens','ensign','estate of','father','father','fr','frau','friar','gen','general','gov','governor','hon','honorable','judge','justice','lieutenant','lieutenant colonel','lieutenant commander','lieutenant general','lieutenant junior grade','lord','lt','ltc','lt cmdr','lt col','lt gen','ltg','lt jg','m','madame','mademoiselle','maj','maj','master sergeant','master sgt','miss','miss','mlle','mme','monsieur','monsignor','monsignor','mr','mr','mr & dr','mr and dr','mr & mrs','mr and mrs','mrs & mr','mrs and mr','ms','ms','msgr','msgr','ofc','officer','president','princess','private','prof','prof & mrs','professor','pvt','rabbi','radm','rear admiral','rep','representative','rev','reverend','reverends','revs','right reverend','rtrev','s sgt','sargent','sec','secretary','sen','senator','senor','senora','senorita','sergeant','sgt','sgt','sheikh','sir','sister','sister','sr','sra','srta','staff sergeant','superintendent','supt','the hon','the honorable','the venerable','treas','treasurer','trust','trustees of','vadm','vice admiral'], (prefix) -> name_prefixes[prefix] = true
_.each ['1','2','3','4','5','6','7','i','ii','iii','iv','v','vi','vii','viii','ix','1st','2nd','3rd','4th','5th','6th','7th','cfx','cnd','cpa','csb','csc','csfn','csj','dc','dds','esq','esquire','first','fs','fsc','ihm','jd','jr','md','ocd','ofm','op','osa','osb','osf','phd','pm','rdc','ret','rsm','second','sj','sm','snd','sp','sr','ssj','us army','us army ret','usa','usa ret','usaf','usaf ret','usaf us air force','usmc us marine corp','usmcr us marine reserves','usn','usn ret','usn us navy','vm'], (suffix) -> name_suffixes[suffix] = true

View file

@ -1,221 +0,0 @@
TaskRegistry = require('../task-registry').default
StoreRegistry = require('../store-registry').default
DatabaseObjectRegistry = require('../database-object-registry').default
# Calling require() repeatedly isn't free! Even though it has it's own cache,
# it still needs to resolve the path to a file based on the current __dirname,
# match it against it's cache, etc. We can shortcut all this work.
RequireCache = {}
class NylasExports
@default = (requireValue) -> requireValue.default ? requireValue
# Will lazy load when requested
@lazyLoad = (prop, path) ->
Object.defineProperty @, prop,
get: ->
key = "#{prop}#{path}"
RequireCache[key] = RequireCache[key] || NylasExports.default(require("../#{path}"))
return RequireCache[key]
enumerable: true
@lazyLoadCustomGetter = (prop, get) ->
Object.defineProperty @, prop, {get, enumerable: true}
@lazyLoadAndRegisterStore = (klassName, path) ->
constructorFactory = ->
NylasExports.default(require("../flux/stores/#{path}"))
StoreRegistry.register(klassName, constructorFactory)
@lazyLoad(klassName, "flux/stores/#{path}")
@lazyLoadAndRegisterModel = (klassName, path) ->
constructorFactory = ->
NylasExports.default(require("../flux/models/#{path}"))
DatabaseObjectRegistry.register(klassName, constructorFactory)
@lazyLoad(klassName, "flux/models/#{path}")
@lazyLoadAndRegisterTask = (klassName, path) ->
constructorFactory = ->
NylasExports.default(require("../flux/tasks/#{path}"))
TaskRegistry.register(klassName, constructorFactory)
@lazyLoad(klassName, "flux/tasks/#{path}")
@lazyLoadDeprecated = (prop, path, {instead} = {}) ->
{deprecate} = require '../deprecate-utils'
Object.defineProperty @, prop,
get: deprecate prop, instead, @, ->
NylasExports.default(require("../#{path}"))
enumerable: true
# Actions
@lazyLoad "Actions", 'flux/actions'
# API Endpoints
@lazyLoad "NylasAPI", 'flux/nylas-api'
@lazyLoad "NylasAPIRequest", 'flux/nylas-api-request'
@lazyLoad "EdgehillAPI", 'flux/edgehill-api'
@lazyLoad "NylasLongConnection", 'flux/nylas-long-connection'
@lazyLoad "NylasSyncStatusStore", 'flux/stores/nylas-sync-status-store'
# The Database
@lazyLoad "Matcher", 'flux/attributes/matcher'
@lazyLoad "DatabaseStore", 'flux/stores/database-store'
@lazyLoad "QueryResultSet", 'flux/models/query-result-set'
@lazyLoad "QuerySubscription", 'flux/models/query-subscription'
@lazyLoad "CalendarDataSource", 'components/nylas-calendar/calendar-data-source'
@lazyLoad "DatabaseTransaction", 'flux/stores/database-transaction'
@lazyLoad "MutableQueryResultSet", 'flux/models/mutable-query-result-set'
@lazyLoad "QuerySubscriptionPool", 'flux/models/query-subscription-pool'
@lazyLoad "ObservableListDataSource", 'flux/stores/observable-list-data-source'
@lazyLoad "MutableQuerySubscription", 'flux/models/mutable-query-subscription'
# Database Objects
@DatabaseObjectRegistry = DatabaseObjectRegistry
@lazyLoad "Model", 'flux/models/model'
@lazyLoad "Attributes", 'flux/attributes'
@lazyLoadAndRegisterModel "File", 'file'
@lazyLoadAndRegisterModel "Event", 'event'
@lazyLoadAndRegisterModel "Label", 'label'
@lazyLoadAndRegisterModel "Folder", 'folder'
@lazyLoadAndRegisterModel "Thread", 'thread'
@lazyLoadAndRegisterModel "Account", 'account'
@lazyLoadAndRegisterModel "Message", 'message'
@lazyLoadAndRegisterModel "Contact", 'contact'
@lazyLoadAndRegisterModel "Category", 'category'
@lazyLoadAndRegisterModel "Calendar", 'calendar'
@lazyLoadAndRegisterModel "JSONBlob", 'json-blob'
# Tasks
@TaskRegistry = TaskRegistry
@lazyLoad "Task", 'flux/tasks/task'
@lazyLoad "TaskFactory", 'flux/tasks/task-factory'
@lazyLoadAndRegisterTask "EventRSVPTask", 'event-rsvp-task'
@lazyLoadAndRegisterTask "BaseDraftTask", 'base-draft-task'
@lazyLoadAndRegisterTask "SendDraftTask", 'send-draft-task'
@lazyLoadAndRegisterTask "ChangeMailTask", 'change-mail-task'
@lazyLoadAndRegisterTask "DestroyDraftTask", 'destroy-draft-task'
@lazyLoadAndRegisterTask "ChangeLabelsTask", 'change-labels-task'
@lazyLoadAndRegisterTask "ChangeFolderTask", 'change-folder-task'
@lazyLoadAndRegisterTask "ChangeUnreadTask", 'change-unread-task'
@lazyLoadAndRegisterTask "DestroyModelTask", 'destroy-model-task'
@lazyLoadAndRegisterTask "SyncbackDraftTask", 'syncback-draft-task'
@lazyLoadAndRegisterTask "ChangeStarredTask", 'change-starred-task'
@lazyLoadAndRegisterTask "SyncbackModelTask", 'syncback-model-task'
@lazyLoadAndRegisterTask "DestroyCategoryTask", 'destroy-category-task'
@lazyLoadAndRegisterTask "SyncbackCategoryTask", 'syncback-category-task'
@lazyLoadAndRegisterTask "SyncbackMetadataTask", 'syncback-metadata-task'
@lazyLoadAndRegisterTask "PerformSendActionTask", 'perform-send-action-task'
@lazyLoadAndRegisterTask "SyncbackDraftFilesTask", 'syncback-draft-files-task'
@lazyLoadAndRegisterTask "ReprocessMailRulesTask", 'reprocess-mail-rules-task'
@lazyLoadAndRegisterTask "NotifyPluginsOfSendTask", 'notify-plugins-of-send-task'
@lazyLoadAndRegisterTask "MultiSendToIndividualTask", 'multi-send-to-individual-task'
@lazyLoadAndRegisterTask "MultiSendSessionCloseTask", 'multi-send-session-close-task'
# Stores
# These need to be required immediately since some Stores are
# listen-only and not explicitly required from anywhere. Stores
# currently set themselves up on require.
@lazyLoadAndRegisterStore "TaskQueue", 'task-queue'
@lazyLoadAndRegisterStore "BadgeStore", 'badge-store'
@lazyLoadAndRegisterStore "DraftStore", 'draft-store'
@lazyLoadAndRegisterStore "ModalStore", 'modal-store'
@lazyLoadAndRegisterStore "OutboxStore", 'outbox-store'
@lazyLoadAndRegisterStore "PopoverStore", 'popover-store'
@lazyLoadAndRegisterStore "AccountStore", 'account-store'
@lazyLoadAndRegisterStore "SignatureStore", 'signature-store'
@lazyLoadAndRegisterStore "MessageStore", 'message-store'
@lazyLoadAndRegisterStore "ContactStore", 'contact-store'
@lazyLoadAndRegisterStore "IdentityStore", 'identity-store'
@lazyLoadAndRegisterStore "MetadataStore", 'metadata-store'
@lazyLoadAndRegisterStore "CategoryStore", 'category-store'
@lazyLoadAndRegisterStore "UndoRedoStore", 'undo-redo-store'
@lazyLoadAndRegisterStore "WorkspaceStore", 'workspace-store'
@lazyLoadAndRegisterStore "MailRulesStore", 'mail-rules-store'
@lazyLoadAndRegisterStore "FileUploadStore", 'file-upload-store'
@lazyLoadAndRegisterStore "SendActionsStore", 'send-actions-store'
@lazyLoadAndRegisterStore "ThreadCountsStore", 'thread-counts-store'
@lazyLoadAndRegisterStore "FileDownloadStore", 'file-download-store'
@lazyLoadAndRegisterStore "UpdateChannelStore", 'update-channel-store'
@lazyLoadAndRegisterStore "PreferencesUIStore", 'preferences-ui-store'
@lazyLoadAndRegisterStore "FocusedContentStore", 'focused-content-store'
@lazyLoadAndRegisterStore "MessageBodyProcessor", 'message-body-processor'
@lazyLoadAndRegisterStore "FocusedContactsStore", 'focused-contacts-store'
@lazyLoadAndRegisterStore "TaskQueueStatusStore", 'task-queue-status-store'
@lazyLoadAndRegisterStore "FocusedPerspectiveStore", 'focused-perspective-store'
@lazyLoadAndRegisterStore "SearchableComponentStore", 'searchable-component-store'
@lazyLoad "CustomContenteditableComponents", 'components/overlaid-components/custom-contenteditable-components'
@lazyLoad "ServiceRegistry", "service-registry"
# Decorators
@lazyLoad "InflatesDraftClientId", 'decorators/inflates-draft-client-id'
# Extensions
@lazyLoad "ExtensionRegistry", 'extension-registry'
@lazyLoad "ComposerExtension", 'extensions/composer-extension'
@lazyLoad "MessageViewExtension", 'extensions/message-view-extension'
@lazyLoad "ContenteditableExtension", 'extensions/contenteditable-extension'
# 3rd party libraries
@lazyLoadCustomGetter "Rx", -> require 'rx-lite'
@lazyLoadCustomGetter "React", -> require 'react'
@lazyLoadCustomGetter "Reflux", -> require 'reflux'
@lazyLoadCustomGetter "ReactDOM", -> require 'react-dom'
@lazyLoadCustomGetter "ReactTestUtils", -> require 'react-addons-test-utils'
@lazyLoadCustomGetter "Keytar", -> require 'keytar' # atom-keytar access through native module
# React Components
@lazyLoad "ComponentRegistry", 'component-registry'
@lazyLoad "PriorityUICoordinator", 'priority-ui-coordinator'
# Utils
@lazyLoad "Utils", 'flux/models/utils'
@lazyLoad "DOMUtils", 'dom-utils'
@lazyLoad "DateUtils", 'date-utils'
@lazyLoad "FsUtils", 'fs-utils'
@lazyLoad "CanvasUtils", 'canvas-utils'
@lazyLoad "RegExpUtils", 'regexp-utils'
@lazyLoad "MenuHelpers", 'menu-helpers'
@lazyLoad "DeprecateUtils", 'deprecate-utils'
@lazyLoad "VirtualDOMUtils", 'virtual-dom-utils'
@lazyLoad "Spellchecker", 'spellchecker'
@lazyLoad "DraftHelpers", 'flux/stores/draft-helpers'
@lazyLoad "MessageUtils", 'flux/models/message-utils'
@lazyLoad "EditorAPI", 'components/contenteditable/editor-api'
# Services
@lazyLoad "SoundRegistry", 'sound-registry'
@lazyLoad "MailRulesTemplates", 'mail-rules-templates'
@lazyLoad "MailRulesProcessor", 'mail-rules-processor'
@lazyLoad "MailboxPerspective", 'mailbox-perspective'
@lazyLoad "NativeNotifications", 'native-notifications'
@lazyLoad "SanitizeTransformer", 'services/sanitize-transformer'
@lazyLoad "QuotedHTMLTransformer", 'services/quoted-html-transformer'
@lazyLoad "InlineStyleTransformer", 'services/inline-style-transformer'
@lazyLoad "SearchableComponentMaker", 'searchable-components/searchable-component-maker'
@lazyLoad "QuotedPlainTextTransformer", 'services/quoted-plain-text-transformer'
# Errors
@lazyLoadCustomGetter "APIError", -> require('../flux/errors').APIError
@lazyLoadCustomGetter "TimeoutError", -> require('../flux/errors').TimeoutError
# Process Internals
@lazyLoad "DefaultClientHelper", 'default-client-helper'
@lazyLoad "BufferedProcess", 'buffered-process'
@lazyLoad "SystemStartService", 'system-start-service'
@lazyLoadCustomGetter "APMWrapper", -> require('../apm-wrapper')
# Testing
@lazyLoadCustomGetter "NylasTestUtils", -> require '../../spec/nylas-test-utils'
# Deprecated
@lazyLoadDeprecated "QuotedHTMLParser", 'services/quoted-html-transformer',
instead: 'QuotedHTMLTransformer'
@lazyLoadDeprecated "DraftStoreExtension", 'flux/stores/draft-store-extension',
instead: 'ComposerExtension'
@lazyLoadDeprecated "MessageStoreExtension", 'flux/stores/message-store-extension',
instead: 'MessageViewExtension'
window.$n = NylasExports
module.exports = NylasExports

View file

@ -0,0 +1,235 @@
/* eslint global-require: 0 */
/* eslint import/no-dynamic-require: 0 */
import TaskRegistry from '../task-registry'
import StoreRegistry from '../store-registry'
import DatabaseObjectRegistry from '../database-object-registry'
const resolveExport = (requireValue) => {
return requireValue.default || requireValue;
}
// This module exports an empty object, with a ton of defined properties that
// `require` files the first time they're called.
module.exports = exports = window.$n = {};
// Calling require() repeatedly isn't free! Even though it has it's own cache,
// it still needs to resolve the path to a file based on the current __dirname,
// match it against it's cache, etc. We can shortcut all this work.
const RequireCache = {};
// Will lazy load when requested
const lazyLoadWithGetter = (prop, getter) => {
const key = `${prop}`;
if (exports[key]) {
throw new Error(`Fatal error: Duplicate entry in nylas-exports: ${key}`)
}
Object.defineProperty(exports, prop, {
get: () => {
RequireCache[key] = RequireCache[key] || getter();
return RequireCache[key];
},
enumerable: true,
});
}
const lazyLoad = (prop, path) => {
lazyLoadWithGetter(prop, () => resolveExport(require(`../${path}`)));
};
const lazyLoadAndRegisterStore = (klassName, path) => {
lazyLoad(klassName, `flux/stores/${path}`);
StoreRegistry.register(klassName, () => exports[klassName]);
}
const lazyLoadAndRegisterModel = (klassName, path) => {
lazyLoad(klassName, `flux/models/${path}`);
DatabaseObjectRegistry.register(klassName, () => exports[klassName]);
};
const lazyLoadAndRegisterTask = (klassName, path) => {
lazyLoad(klassName, `flux/tasks/${path}`);
TaskRegistry.register(klassName, () => exports[klassName]);
};
const lazyLoadDeprecated = (prop, path, {instead} = {}) => {
const {deprecate} = require('../deprecate-utils');
Object.defineProperty(exports, prop, {
get: deprecate(prop, instead, exports, () => {
return resolveExport(require(`../${path}`));
}),
enumerable: true,
});
};
// Actions
lazyLoad(`Actions`, 'flux/actions');
// API Endpoints
lazyLoad(`NylasAPI`, 'flux/nylas-api');
lazyLoad(`NylasAPIRequest`, 'flux/nylas-api-request');
lazyLoad(`EdgehillAPI`, 'flux/edgehill-api');
lazyLoad(`NylasLongConnection`, 'flux/nylas-long-connection');
lazyLoad(`NylasSyncStatusStore`, 'flux/stores/nylas-sync-status-store');
// The Database
lazyLoad(`Matcher`, 'flux/attributes/matcher');
lazyLoad(`DatabaseStore`, 'flux/stores/database-store');
lazyLoad(`QueryResultSet`, 'flux/models/query-result-set');
lazyLoad(`QuerySubscription`, 'flux/models/query-subscription');
lazyLoad(`CalendarDataSource`, 'components/nylas-calendar/calendar-data-source');
lazyLoad(`DatabaseTransaction`, 'flux/stores/database-transaction');
lazyLoad(`MutableQueryResultSet`, 'flux/models/mutable-query-result-set');
lazyLoad(`QuerySubscriptionPool`, 'flux/models/query-subscription-pool');
lazyLoad(`ObservableListDataSource`, 'flux/stores/observable-list-data-source');
lazyLoad(`MutableQuerySubscription`, 'flux/models/mutable-query-subscription');
// Database Objects
exports.DatabaseObjectRegistry = DatabaseObjectRegistry;
lazyLoad(`Model`, 'flux/models/model');
lazyLoad(`Attributes`, 'flux/attributes');
lazyLoadAndRegisterModel(`File`, 'file');
lazyLoadAndRegisterModel(`Event`, 'event');
lazyLoadAndRegisterModel(`Label`, 'label');
lazyLoadAndRegisterModel(`Folder`, 'folder');
lazyLoadAndRegisterModel(`Thread`, 'thread');
lazyLoadAndRegisterModel(`Account`, 'account');
lazyLoadAndRegisterModel(`Message`, 'message');
lazyLoadAndRegisterModel(`Contact`, 'contact');
lazyLoadAndRegisterModel(`Category`, 'category');
lazyLoadAndRegisterModel(`Calendar`, 'calendar');
lazyLoadAndRegisterModel(`JSONBlob`, 'json-blob');
// Tasks
exports.TaskRegistry = TaskRegistry;
lazyLoad(`Task`, 'flux/tasks/task');
lazyLoad(`TaskFactory`, 'flux/tasks/task-factory');
lazyLoadAndRegisterTask(`EventRSVPTask`, 'event-rsvp-task');
lazyLoadAndRegisterTask(`BaseDraftTask`, 'base-draft-task');
lazyLoadAndRegisterTask(`SendDraftTask`, 'send-draft-task');
lazyLoadAndRegisterTask(`ChangeMailTask`, 'change-mail-task');
lazyLoadAndRegisterTask(`DestroyDraftTask`, 'destroy-draft-task');
lazyLoadAndRegisterTask(`ChangeLabelsTask`, 'change-labels-task');
lazyLoadAndRegisterTask(`ChangeFolderTask`, 'change-folder-task');
lazyLoadAndRegisterTask(`ChangeUnreadTask`, 'change-unread-task');
lazyLoadAndRegisterTask(`DestroyModelTask`, 'destroy-model-task');
lazyLoadAndRegisterTask(`SyncbackDraftTask`, 'syncback-draft-task');
lazyLoadAndRegisterTask(`ChangeStarredTask`, 'change-starred-task');
lazyLoadAndRegisterTask(`SyncbackModelTask`, 'syncback-model-task');
lazyLoadAndRegisterTask(`DestroyCategoryTask`, 'destroy-category-task');
lazyLoadAndRegisterTask(`SyncbackCategoryTask`, 'syncback-category-task');
lazyLoadAndRegisterTask(`SyncbackMetadataTask`, 'syncback-metadata-task');
lazyLoadAndRegisterTask(`PerformSendActionTask`, 'perform-send-action-task');
lazyLoadAndRegisterTask(`SyncbackDraftFilesTask`, 'syncback-draft-files-task');
lazyLoadAndRegisterTask(`ReprocessMailRulesTask`, 'reprocess-mail-rules-task');
lazyLoadAndRegisterTask(`NotifyPluginsOfSendTask`, 'notify-plugins-of-send-task');
lazyLoadAndRegisterTask(`MultiSendToIndividualTask`, 'multi-send-to-individual-task');
lazyLoadAndRegisterTask(`MultiSendSessionCloseTask`, 'multi-send-session-close-task');
// Stores
// These need to be required immediately since some Stores are
// listen-only and not explicitly required from anywhere. Stores
// currently set themselves up on require.
lazyLoadAndRegisterStore(`TaskQueue`, 'task-queue');
lazyLoadAndRegisterStore(`BadgeStore`, 'badge-store');
lazyLoadAndRegisterStore(`DraftStore`, 'draft-store');
lazyLoadAndRegisterStore(`ModalStore`, 'modal-store');
lazyLoadAndRegisterStore(`OutboxStore`, 'outbox-store');
lazyLoadAndRegisterStore(`PopoverStore`, 'popover-store');
lazyLoadAndRegisterStore(`AccountStore`, 'account-store');
lazyLoadAndRegisterStore(`SignatureStore`, 'signature-store');
lazyLoadAndRegisterStore(`MessageStore`, 'message-store');
lazyLoadAndRegisterStore(`ContactStore`, 'contact-store');
lazyLoadAndRegisterStore(`IdentityStore`, 'identity-store');
lazyLoadAndRegisterStore(`MetadataStore`, 'metadata-store');
lazyLoadAndRegisterStore(`CategoryStore`, 'category-store');
lazyLoadAndRegisterStore(`UndoRedoStore`, 'undo-redo-store');
lazyLoadAndRegisterStore(`WorkspaceStore`, 'workspace-store');
lazyLoadAndRegisterStore(`MailRulesStore`, 'mail-rules-store');
lazyLoadAndRegisterStore(`FileUploadStore`, 'file-upload-store');
lazyLoadAndRegisterStore(`SendActionsStore`, 'send-actions-store');
lazyLoadAndRegisterStore(`ThreadCountsStore`, 'thread-counts-store');
lazyLoadAndRegisterStore(`FileDownloadStore`, 'file-download-store');
lazyLoadAndRegisterStore(`UpdateChannelStore`, 'update-channel-store');
lazyLoadAndRegisterStore(`PreferencesUIStore`, 'preferences-ui-store');
lazyLoadAndRegisterStore(`FocusedContentStore`, 'focused-content-store');
lazyLoadAndRegisterStore(`MessageBodyProcessor`, 'message-body-processor');
lazyLoadAndRegisterStore(`FocusedContactsStore`, 'focused-contacts-store');
lazyLoadAndRegisterStore(`TaskQueueStatusStore`, 'task-queue-status-store');
lazyLoadAndRegisterStore(`FocusedPerspectiveStore`, 'focused-perspective-store');
lazyLoadAndRegisterStore(`SearchableComponentStore`, 'searchable-component-store');
lazyLoad(`CustomContenteditableComponents`, 'components/overlaid-components/custom-contenteditable-components');
lazyLoad(`ServiceRegistry`, `service-registry`);
// Decorators
lazyLoad(`InflatesDraftClientId`, 'decorators/inflates-draft-client-id');
// Extensions
lazyLoad(`ExtensionRegistry`, 'extension-registry');
lazyLoad(`ComposerExtension`, 'extensions/composer-extension');
lazyLoad(`MessageViewExtension`, 'extensions/message-view-extension');
lazyLoad(`ContenteditableExtension`, 'extensions/contenteditable-extension');
// 3rd party libraries
lazyLoadWithGetter(`Rx`, () => require('rx-lite'));
lazyLoadWithGetter(`React`, () => require('react'));
lazyLoadWithGetter(`Reflux`, () => require('reflux'));
lazyLoadWithGetter(`ReactDOM`, () => require('react-dom'));
lazyLoadWithGetter(`ReactTestUtils`, () => require('react-addons-test-utils'));
lazyLoadWithGetter(`Keytar`, () => require('keytar')); // atom-keytar access through native module
// React Components
lazyLoad(`ComponentRegistry`, 'component-registry');
lazyLoad(`PriorityUICoordinator`, 'priority-ui-coordinator');
// Utils
lazyLoad(`Utils`, 'flux/models/utils');
lazyLoad(`DOMUtils`, 'dom-utils');
lazyLoad(`DateUtils`, 'date-utils');
lazyLoad(`FsUtils`, 'fs-utils');
lazyLoad(`CanvasUtils`, 'canvas-utils');
lazyLoad(`RegExpUtils`, 'regexp-utils');
lazyLoad(`MenuHelpers`, 'menu-helpers');
lazyLoad(`DeprecateUtils`, 'deprecate-utils');
lazyLoad(`VirtualDOMUtils`, 'virtual-dom-utils');
lazyLoad(`Spellchecker`, 'spellchecker');
lazyLoad(`DraftHelpers`, 'flux/stores/draft-helpers');
lazyLoad(`MessageUtils`, 'flux/models/message-utils');
lazyLoad(`EditorAPI`, 'components/contenteditable/editor-api');
// Services
lazyLoad(`SoundRegistry`, 'sound-registry');
lazyLoad(`MailRulesTemplates`, 'mail-rules-templates');
lazyLoad(`MailRulesProcessor`, 'mail-rules-processor');
lazyLoad(`MailboxPerspective`, 'mailbox-perspective');
lazyLoad(`NativeNotifications`, 'native-notifications');
lazyLoad(`SanitizeTransformer`, 'services/sanitize-transformer');
lazyLoad(`QuotedHTMLTransformer`, 'services/quoted-html-transformer');
lazyLoad(`InlineStyleTransformer`, 'services/inline-style-transformer');
lazyLoad(`SearchableComponentMaker`, 'searchable-components/searchable-component-maker');
lazyLoad(`QuotedPlainTextTransformer`, 'services/quoted-plain-text-transformer');
// Errors
lazyLoadWithGetter(`APIError`, () => require('../flux/errors').APIError);
lazyLoadWithGetter(`TimeoutError`, () => require('../flux/errors').TimeoutError);
// Process Internals
lazyLoad(`DefaultClientHelper`, 'default-client-helper');
lazyLoad(`BufferedProcess`, 'buffered-process');
lazyLoad(`SystemStartService`, 'system-start-service');
lazyLoadWithGetter(`APMWrapper`, () => require('../apm-wrapper'));
// Testing
lazyLoadWithGetter(`NylasTestUtils`, () => require('../../spec/nylas-test-utils'));
// Deprecated
lazyLoadDeprecated(`QuotedHTMLParser`, 'services/quoted-html-transformer', {
instead: 'QuotedHTMLTransformer',
});
lazyLoadDeprecated(`DraftStoreExtension`, 'flux/stores/draft-store-extension', {
instead: 'ComposerExtension',
});
lazyLoadDeprecated(`MessageStoreExtension`, 'flux/stores/message-store-extension', {
instead: 'MessageViewExtension',
});

View file

@ -143,7 +143,7 @@ class NylasEnvConstructor
PackageManager = require './package-manager'
ThemeManager = require './theme-manager'
StyleManager = require './style-manager'
ActionBridge = require './flux/action-bridge'
ActionBridge = require('./flux/action-bridge').default
MenuManager = require('./menu-manager').default
{devMode, safeMode, resourcePath, configDirPath, windowType} = @getLoadSettings()

View file

@ -2,7 +2,7 @@ React = require 'react'
ReactCSSTransitionGroup = require 'react-addons-css-transition-group'
Sheet = require './sheet'
Toolbar = require './sheet-toolbar'
Flexbox = require './components/flexbox'
Flexbox = require('./components/flexbox').default
RetinaImg = require('./components/retina-img').default
InjectedComponentSet = require './components/injected-component-set'
_ = require 'underscore'

View file

@ -1,7 +1,7 @@
React = require 'react'
ReactDOM = require 'react-dom'
Sheet = require './sheet'
Flexbox = require './components/flexbox'
Flexbox = require('./components/flexbox').default
RetinaImg = require('./components/retina-img').default
Utils = require './flux/models/utils'
{remote} = require 'electron'

View file

@ -2,7 +2,7 @@ React = require 'react'
_ = require 'underscore'
{Actions,ComponentRegistry, WorkspaceStore} = require "nylas-exports"
RetinaImg = require('./components/retina-img').default
Flexbox = require './components/flexbox'
Flexbox = require('./components/flexbox').default
InjectedComponentSet = require './components/injected-component-set'
ResizableRegion = require './components/resizable-region'