refactor(react): convert to class-based React components

Summary: Fix react upgrade errors

Test Plan: edgehill --test

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://review.inboxapp.com/D1456
This commit is contained in:
Evan Morikawa 2015-04-30 13:08:29 -07:00
parent 57cb02c76a
commit e3dfbe59be
49 changed files with 611 additions and 751 deletions

View file

@ -2,9 +2,11 @@ React = require 'react'
{Actions} = require "inbox-exports"
module.exports =
AccountSidebarDividerItem = React.createClass
class AccountSidebarDividerItem extends React.Component
displayName: 'AccountSidebarDividerItem'
render: ->
render: =>
<div className="item item-divider">{@props.label}</div>
module.exports = AccountSidebarDividerItem

View file

@ -1,14 +1,18 @@
React = require 'react'
{Actions} = require("inbox-exports")
module.exports =
AccountSidebarItem = React.createClass
render: ->
class AccountSidebarItem extends React.Component
@displayName: "AccountSidebarItem"
render: =>
className = "item " + if @props.select then " selected" else ""
<div className={className} onClick={@_onClick} id={@props.item.id}>
<span className="name"> {@props.item.name}</span>
</div>
_onClick: (event) ->
_onClick: (event) =>
event.preventDefault()
Actions.selectView(@props.item.view)
module.exports = AccountSidebarItem

View file

@ -3,11 +3,10 @@ classNames = require 'classnames'
{Actions, Utils, WorkspaceStore} = require 'inbox-exports'
{RetinaImg} = require 'ui-components'
module.exports =
AccountSidebarSheetItem = React.createClass
displayName: 'AccountSidebarSheetItem'
class AccountSidebarSheetItem extends React.Component
@displayName: 'AccountSidebarSheetItem'
render: ->
render: =>
classSet = classNames
'item': true
'selected': @props.select
@ -17,6 +16,9 @@ AccountSidebarSheetItem = React.createClass
<span className="name"> {@props.item.name}</span>
</div>
_onClick: (event) ->
_onClick: (event) =>
event.preventDefault()
Actions.selectRootSheet(@props.item)
module.exports = AccountSidebarSheetItem

View file

@ -3,16 +3,15 @@ classNames = require 'classnames'
{Actions, Utils, WorkspaceStore} = require 'inbox-exports'
{RetinaImg} = require 'ui-components'
module.exports =
AccountSidebarTagItem = React.createClass
displayName: 'AccountSidebarTagItem'
class AccountSidebarTagItem extends React.Component
@displayName: 'AccountSidebarTagItem'
shouldComponentUpdate: (nextProps) ->
shouldComponentUpdate: (nextProps) =>
@props?.item.id isnt nextProps.item.id or
@props?.item.unreadCount isnt nextProps.item.unreadCount or
@props?.select isnt nextProps.select
render: ->
render: =>
unread = []
if @props.item.unreadCount > 0
unread = <div className="unread item-count-box">{@props.item.unreadCount}</div>
@ -27,7 +26,10 @@ AccountSidebarTagItem = React.createClass
{unread}
</div>
_onClick: (event) ->
_onClick: (event) =>
event.preventDefault()
Actions.selectRootSheet(WorkspaceStore.Sheet.Threads)
Actions.focusTag(@props.item)
module.exports = AccountSidebarTagItem

View file

@ -5,37 +5,36 @@ SidebarTagItem = require("./account-sidebar-tag-item")
SidebarSheetItem = require("./account-sidebar-sheet-item")
SidebarStore = require ("./account-sidebar-store")
module.exports =
AccountSidebar = React.createClass
displayName: 'AccountSidebar'
class AccountSidebar extends React.Component
@displayName: 'AccountSidebar'
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = SidebarStore.listen @_onStoreChange
# It's important that every React class explicitly stops listening to
# atom events before it unmounts. Thank you event-kit
# This can be fixed via a Reflux mixin
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe() if @unsubscribe
render: ->
render: =>
<div id="account-sidebar" className="account-sidebar">
<div className="account-sidebar-sections">
{@_sections()}
</div>
</div>
_sections: ->
_sections: =>
return @state.sections.map (section) =>
<section key={section.label}>
<div className="heading">{section.label}</div>
{@_itemComponents(section)}
</section>
_itemComponents: (section) ->
_itemComponents: (section) =>
if section.type is 'tag'
itemClass = SidebarTagItem
else if section.type is 'sheet'
@ -49,13 +48,16 @@ AccountSidebar = React.createClass
item={item}
select={item.id is @state.selected?.id }/>
_onStoreChange: ->
_onStoreChange: =>
@setState @_getStateFromStores()
_getStateFromStores: ->
_getStateFromStores: =>
sections: SidebarStore.sections()
selected: SidebarStore.selected()
AccountSidebar.minWidth = 165
AccountSidebar.maxWidth = 190
module.exports = AccountSidebar

View file

@ -7,18 +7,17 @@ React = require 'react'
# @props.download is a FileDownloadStore.Download object
# @props.file is a File object
module.exports =
AttachmentComponent = React.createClass
displayName: 'AttachmentComponent'
class AttachmentComponent extends React.Component
@displayName: 'AttachmentComponent'
propTypes:
@propTypes:
file: React.PropTypes.object.isRequired,
download: React.PropTypes.object
getInitialState: ->
progressPercent: 0
constructor: (@props) ->
@state = progressPercent: 0
render: ->
render: =>
<div className={"attachment-file-wrap " + (@props.download?.state() ? "")}>
<span className="attachment-download-bar-wrap">
<span className="attachment-bar-bg"></span>
@ -36,7 +35,7 @@ AttachmentComponent = React.createClass
</div>
_fileActions: ->
_fileActions: =>
if @props.removable
<button className="btn btn-icon attachment-icon" onClick={@_onClickRemove}>
<i className="fa fa-remove"></i>
@ -50,20 +49,23 @@ AttachmentComponent = React.createClass
<i className="fa fa-download"></i>
</button>
_downloadProgressStyle: ->
_downloadProgressStyle: =>
width: @props.download?.percent ? 0
_onClickRemove: ->
_onClickRemove: =>
Actions.removeFile
file: @props.file
messageLocalId: @props.messageLocalId
_onClickView: -> Actions.fetchAndOpenFile(@props.file) if @_canClickToView()
_onClickView: => Actions.fetchAndOpenFile(@props.file) if @_canClickToView()
_onClickDownload: -> Actions.fetchAndSaveFile(@props.file)
_onClickDownload: => Actions.fetchAndSaveFile(@props.file)
_onClickAbort: -> Actions.abortDownload(@props.file, @props.download)
_onClickAbort: => Actions.abortDownload(@props.file, @props.download)
_canClickToView: -> not @props.removable and not @_isDownloading()
_canClickToView: => not @props.removable and not @_isDownloading()
_isDownloading: -> @props.download?.state() is "downloading"
_isDownloading: => @props.download?.state() is "downloading"
module.exports = AttachmentComponent

View file

@ -2,9 +2,8 @@ React = require 'react'
{Actions} = require("inbox-exports")
moment = require 'moment'
module.exports =
CalendarBarItem = React.createClass
render: ->
class CalendarBarItem extends React.Component
render: =>
style =
left: @props.item.xPercent
top: @props.item.yPercent
@ -16,7 +15,7 @@ CalendarBarItem = React.createClass
<span className="time">{@_time()}</span>
</div>
_time: ->
_time: =>
w = @props.item.event.when
if w.start_time
return moment.unix(w.start_time).format('h:mm a')
@ -27,6 +26,9 @@ CalendarBarItem = React.createClass
else
return ""
_onClick: (event) ->
_onClick: (event) =>
event.preventDefault()
Actions.focusTag(@props.tag)
module.exports = CalendarBarItem

View file

@ -11,35 +11,37 @@ class CalendarBarRow
@last = initialItem.event.end
@items.push(initialItem)
canHoldItem: (item) ->
canHoldItem: (item) =>
item.event.start > @last
addItem: (item) ->
addItem: (item) =>
@last = item.event.end
@items.push(item)
CalendarBarMarker = React.createClass
render: ->
class CalendarBarMarker extends React.Component
@displayName: "CalendarBarMarker"
render: =>
classname = "marker"
classname += " now" if @props.marker.now
<div className={classname} style={left: @props.marker.xPercent} id={@props.marker.xPercent}/>
module.exports =
CalendarBar = React.createClass
class CalendarBar extends React.Component
@displayName: "CalendarBar"
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = CalendarBarEventStore.listen @_onStoreChange
# It's important that every React class explicitly stops listening to
# atom events before it unmounts. Thank you event-kit
# This can be fixed via a Reflux mixin
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe() if @unsubscribe
render: ->
render: =>
markers = @_getMarkers().map (marker) ->
<CalendarBarMarker marker={marker}/>
@ -52,14 +54,14 @@ CalendarBar = React.createClass
{items}
</div>
_onStoreChange: ->
_onStoreChange: =>
@setState @_getStateFromStores()
_getStateFromStores: ->
_getStateFromStores: =>
events: CalendarBarEventStore.events()
range: CalendarBarEventStore.range()
_getMarkers: ->
_getMarkers: =>
range = @state.range
now = (new Date).getTime()/1000 - range.start
markers = []
@ -72,7 +74,7 @@ CalendarBar = React.createClass
xPercent: (now * 100) / (range.end - range.start) + "%"
markers
_getItemsForEvents: (events) ->
_getItemsForEvents: (events) =>
# Create an array of items with additional metadata needed for our view.
# We compute the X and width of elements using their durations as a fraction
# of the displayed range
@ -121,3 +123,5 @@ CalendarBar = React.createClass
item.hPercent = (100.0 / item.rowCount) + "%"
items
module.exports = CalendarBar

View file

@ -19,27 +19,27 @@ ParticipantsTextField = require './participants-text-field'
# The ComposerView is a unique React component because it (currently) is a
# singleton. Normally, the React way to do things would be to re-render the
# Composer with new props.
module.exports =
ComposerView = React.createClass
displayName: 'ComposerView'
class ComposerView extends React.Component
@displayName: 'ComposerView'
getInitialState: ->
populated: false
to: []
cc: []
bcc: []
body: ""
subject: ""
showcc: false
showbcc: false
showsubject: false
showQuotedText: false
isSending: DraftStore.sendingState(@props.localId)
constructor: (@props) ->
@state =
populated: false
to: []
cc: []
bcc: []
body: ""
subject: ""
showcc: false
showbcc: false
showsubject: false
showQuotedText: false
isSending: DraftStore.sendingState(@props.localId)
componentWillMount: ->
componentWillMount: =>
@_prepareForDraft(@props.localId)
componentDidMount: ->
componentDidMount: =>
@_draftStoreUnlisten = DraftStore.listen @_onSendingStateChanged
@_keymapUnlisten = atom.commands.add '.composer-outer-wrap', {
'composer:show-and-focus-bcc': @_showAndFocusBcc
@ -55,12 +55,12 @@ ComposerView = React.createClass
# work unless the element is on the page.
@focus "textFieldTo"
componentWillUnmount: ->
componentWillUnmount: =>
@_teardownForDraft()
@_draftStoreUnlisten() if @_draftStoreUnlisten
@_keymapUnlisten.dispose() if @_keymapUnlisten
componentDidUpdate: ->
componentDidUpdate: =>
# We want to use a temporary variable instead of putting this into the
# state. This is because the selection is a transient property that
# only needs to be applied once. It's not a long-living property of
@ -68,7 +68,7 @@ ComposerView = React.createClass
# re-rendering.
@_recoveredSelection = null if @_recoveredSelection?
componentWillReceiveProps: (newProps) ->
componentWillReceiveProps: (newProps) =>
if newProps.localId isnt @props.localId
# When we're given a new draft localId, we have to stop listening to our
# current DraftStoreProxy, create a new one and listen to that. The simplest
@ -76,7 +76,7 @@ ComposerView = React.createClass
@_teardownForDraft()
@_prepareForDraft(newProps.localId)
_prepareForDraft: (localId) ->
_prepareForDraft: (localId) =>
@unlisteners = []
return unless localId
@ -87,12 +87,12 @@ ComposerView = React.createClass
if @_proxy.draft()
@_onDraftChanged()
_teardownForDraft: ->
_teardownForDraft: =>
unlisten() for unlisten in @unlisteners
if @_proxy
@_proxy.changes.commit()
render: ->
render: =>
if @props.mode is "inline"
<div className={@_wrapClasses()}>
<ResizableRegion handle={ResizableRegion.Handle.Bottom}>
@ -104,10 +104,10 @@ ComposerView = React.createClass
{@_renderComposer()}
</div>
_wrapClasses: ->
_wrapClasses: =>
"composer-outer-wrap #{@props.className ? ""}"
_renderComposer: ->
_renderComposer: =>
<div className="composer-inner-wrap" onDragOver={@_onDragNoop} onDragLeave={@_onDragNoop} onDragEnd={@_onDragNoop} onDrop={@_onDrop}>
<div className="composer-cover"
@ -139,6 +139,7 @@ ComposerView = React.createClass
<ParticipantsTextField
ref="textFieldTo"
field='to'
visible={true}
change={@_onChangeParticipants}
participants={to: @state['to'], cc: @state['cc'], bcc: @state['bcc']}
tabIndex='102'/>
@ -193,7 +194,7 @@ ComposerView = React.createClass
</div>
</div>
_renderFooterRegions: ->
_renderFooterRegions: =>
return <div></div> unless @props.localId
<span>
@ -211,7 +212,7 @@ ComposerView = React.createClass
<InjectedComponentSet location="Composer:Footer" draftLocalId={@props.localId}/>
</span>
_renderActionsRegion: ->
_renderActionsRegion: =>
return <div></div> unless @props.localId
<InjectedComponentSet className="composer-action-bar-content"
@ -238,8 +239,7 @@ ComposerView = React.createClass
# Focus the composer view. Chooses the appropriate field to start
# focused depending on the draft type, or you can pass a field as
# the first parameter.
focus: (field = null) ->
return unless @isMounted()
focus: (field = null) =>
if component?.isForwardedMessage()
field ?= "textFieldTo"
@ -247,15 +247,14 @@ ComposerView = React.createClass
field ?= "contentBody"
_.delay =>
return unless @isMounted()
@refs[field]?.focus?()
, 150
isForwardedMessage: ->
isForwardedMessage: =>
draft = @_proxy.draft()
Utils.isForwardedMessage(draft)
_onDraftChanged: ->
_onDraftChanged: =>
draft = @_proxy.draft()
if not @_initialHistorySave
@_saveToHistory()
@ -278,33 +277,33 @@ ComposerView = React.createClass
@setState(state)
_shouldShowSubject: ->
_shouldShowSubject: =>
draft = @_proxy.draft()
if _.isEmpty(draft.subject ? "") then return true
else if @isForwardedMessage() then return true
else return false
_onDragNoop: (e) ->
_onDragNoop: (e) =>
e.preventDefault()
_onDrop: (e) ->
_onDrop: (e) =>
e.preventDefault()
for file in e.dataTransfer.files
Actions.attachFilePath({path: file.path, messageLocalId: @props.localId})
true
_onChangeParticipants: (changes={}) -> @_addToProxy(changes)
_onChangeSubject: (event) -> @_addToProxy(subject: event.target.value)
_onChangeParticipants: (changes={}) => @_addToProxy(changes)
_onChangeSubject: (event) => @_addToProxy(subject: event.target.value)
_onChangeBody: (event) ->
_onChangeBody: (event) =>
if @_getSelections().currentSelection?.atEndOfContent
@props.onRequestScrollTo?(messageId: @_proxy.draft().id, location: "bottom")
@_addToProxy(body: event.target.value)
_onChangeEditableMode: ({showQuotedText}) ->
_onChangeEditableMode: ({showQuotedText}) =>
@setState showQuotedText: showQuotedText
_addToProxy: (changes={}, source={}) ->
_addToProxy: (changes={}, source={}) =>
return unless @_proxy
selections = @_getSelections()
@ -315,11 +314,11 @@ ComposerView = React.createClass
@_saveToHistory(selections) unless source.fromUndoManager
_popoutComposer: ->
_popoutComposer: =>
@_proxy.changes.commit()
Actions.composePopoutDraft @props.localId
_sendDraft: (options = {}) ->
_sendDraft: (options = {}) =>
return if @state.isSending
draft = @_proxy.draft()
remote = require('remote')
@ -358,7 +357,7 @@ ComposerView = React.createClass
Actions.sendDraft(@props.localId)
_hasAttachment: (body) ->
_hasAttachment: (body) =>
body = body.toLowerCase().trim()
attachIndex = body.indexOf("attach")
if attachIndex >= 0
@ -368,25 +367,25 @@ ComposerView = React.createClass
else return true
else return false
_destroyDraft: ->
_destroyDraft: =>
Actions.destroyDraft(@props.localId)
_attachFile: ->
_attachFile: =>
Actions.attachFile({messageLocalId: @props.localId})
_showAndFocusBcc: ->
_showAndFocusBcc: =>
@setState {showbcc: true}
@focus "textFieldBcc"
_showAndFocusCc: ->
_showAndFocusCc: =>
@setState {showcc: true}
@focus "textFieldCc"
_onSendingStateChanged: ->
_onSendingStateChanged: =>
@setState isSending: DraftStore.sendingState(@props.localId)
undo: (event) ->
undo: (event) =>
event.preventDefault()
event.stopPropagation()
historyItem = @undoManager.undo() ? {}
@ -395,7 +394,7 @@ ComposerView = React.createClass
@_recoveredSelection = historyItem.currentSelection
@_addToProxy historyItem.state, fromUndoManager: true
redo: (event) ->
redo: (event) =>
event.preventDefault()
event.stopPropagation()
historyItem = @undoManager.redo() ? {}
@ -404,11 +403,11 @@ ComposerView = React.createClass
@_recoveredSelection = historyItem.currentSelection
@_addToProxy historyItem.state, fromUndoManager: true
_getSelections: ->
_getSelections: =>
currentSelection: @refs.contentBody?.getCurrentSelection?()
previousSelection: @refs.contentBody?.getPreviousSelection?()
_saveToHistory: (selections) ->
_saveToHistory: (selections) =>
selections ?= @_getSelections()
newDraft = @_proxy.draft()
@ -429,5 +428,8 @@ ComposerView = React.createClass
@undoManager.saveToHistory(historyItem)
_deleteEmptyDraft: ->
_deleteEmptyDraft: =>
if @_proxy.draft().pristine then Actions.destroyDraft(@props.localId)
module.exports = ComposerView

View file

@ -1,41 +0,0 @@
# OBSOLETE: Use FloatingToolbar instead.
# However, We may decide to change back to a static toolbar in the footer,
# so don't delete me yet.
# React = require 'react'
#
# module.exports =
# ContenteditableToolbar = React.createClass
# render: =>
# style =
# display: @state.show and 'initial' or 'none'
# <div className="compose-toolbar-wrap" onBlur={@onBlur}>
# <button className="btn btn-icon btn-formatting"
# onClick={=> @setState { show: !@state.show }}
# ><i className="fa fa-font"></i></button>
# <div ref="toolbar" className="compose-toolbar" style={style}>
# <button className="btn btn-bold" onClick={@onClick} data-command-name="bold"><strong>B</strong></button>
# <button className="btn btn-italic" onClick={@onClick} data-command-name="italic"><em>I</em></button>
# <button className="btn btn-underline" onClick={@onClick} data-command-name="underline"><span style={'textDecoration': 'underline'}>U</span></button>
# </div>
# </div>
#
# getInitialState: =>
# show: false
#
# componentDidUpdate: (lastProps, lastState) =>
# if !lastState.show and @state.show
# React.findDOMNode(@refs.toolbar).focus()
#
# onClick: (event) =>
# cmd = event.currentTarget.getAttribute 'data-command-name'
# document.execCommand(cmd, false, null)
# true
#
# onBlur: (event) =>
# target = event.nativeEvent.relatedTarget
# if target? and target.getAttribute 'data-command-name'
# return
# @setState
# show: false

View file

@ -3,8 +3,8 @@ React = require 'react'
{Actions,
FileUploadStore} = require 'inbox-exports'
FileUpload = React.createClass
render: ->
class FileUpload extends React.Component
render: =>
<div className={"attachment-file-wrap " + @props.uploadData.state}>
<span className="attachment-bar-bg"></span>
<span className="attachment-upload-progress" style={@_uploadProgressStyle()}></span>
@ -19,40 +19,40 @@ FileUpload = React.createClass
</span>
</div>
_uploadProgressStyle: ->
_uploadProgressStyle: =>
if @props.uploadData.fileSize <= 0
percent = 0
else
percent = (@props.uploadData.bytesUploaded / @props.uploadData.fileSize) * 100
width: "#{percent}%"
_onClickRemove: ->
_onClickRemove: =>
Actions.abortUpload @props.uploadData
_basename: ->
_basename: =>
path.basename(@props.uploadData.filePath)
module.exports =
FileUploads = React.createClass
getInitialState: ->
uploads: FileUploadStore.uploadsForMessage(@props.localId) ? []
class FileUploads extends React.Component
constructor: (@props) ->
@state =
uploads: FileUploadStore.uploadsForMessage(@props.localId) ? []
componentDidMount: ->
componentDidMount: =>
@storeUnlisten = FileUploadStore.listen(@_onFileUploadStoreChange)
componentWillUnmount: ->
componentWillUnmount: =>
@storeUnlisten() if @storeUnlisten
render: ->
render: =>
<span className="file-uploads">
{@_fileUploads()}
</span>
_fileUploads: ->
_fileUploads: =>
@state.uploads.map (uploadData) =>
<FileUpload key={@_key(uploadData)} uploadData={uploadData} />
_key: (uploadData) ->
_key: (uploadData) =>
"#{uploadData.messageLocalId} #{uploadData.filePath}"
# fileUploads:
@ -63,5 +63,7 @@ FileUploads = React.createClass
# fileName - The basename of the file
# bytesUploaded - Current number of bytes uploaded
# state - one of "started" "progress" "completed" "aborted" "failed"
_onFileUploadStoreChange: ->
_onFileUploadStoreChange: =>
@setState uploads: FileUploadStore.uploadsForMessage(@props.localId)
module.exports = FileUploads

View file

@ -2,9 +2,8 @@ React = require 'react'
{Message, Actions, NamespaceStore} = require 'inbox-exports'
{RetinaImg} = require 'ui-components'
module.exports =
NewComposeButton = React.createClass
render: ->
class NewComposeButton extends React.Component
render: =>
<button style={order: 101}
className="btn btn-toolbar"
data-tooltip="Compose new message"
@ -12,4 +11,6 @@ NewComposeButton = React.createClass
<RetinaImg name="toolbar-compose.png"/>
</button>
_onNewCompose: -> Actions.composeNewBlankDraft()
_onNewCompose: => Actions.composeNewBlankDraft()
module.exports = NewComposeButton

View file

@ -6,11 +6,10 @@ _ = require 'underscore-plus'
ContactStore} = require 'inbox-exports'
{TokenizingTextField, Menu} = require 'ui-components'
module.exports =
ParticipantsTextField = React.createClass
displayName: 'ParticipantsTextField'
class ParticipantsTextField extends React.Component
@displayName: 'ParticipantsTextField'
propTypes:
@propTypes:
# The tab index of the ParticipantsTextField
tabIndex: React.PropTypes.string,
@ -31,10 +30,10 @@ ParticipantsTextField = React.createClass
# changes are made.
change: React.PropTypes.func.isRequired,
getDefaultProps: ->
getDefaultProps: =>
visible: true
render: ->
render: =>
classSet = {}
classSet[@props.field] = true
<div className="participants-text-field" style={zIndex: 1000-@props.tabIndex, display: @props.visible and 'inline' or 'none'}>
@ -57,12 +56,12 @@ ParticipantsTextField = React.createClass
# Public. Can be called by any component that has a ref to this one to
# focus the input field.
focus: -> @refs.textField.focus()
focus: => @refs.textField.focus()
_completionNode: (p) ->
_completionNode: (p) =>
<Menu.NameEmailItem name={p.name} email={p.email} />
_tokenNode: (p) ->
_tokenNode: (p) =>
if p.name?.length > 0 and p.name isnt p.email
<div className="participant">
<span className="participant-primary">{p.name}</span>&nbsp;&nbsp;
@ -74,7 +73,7 @@ ParticipantsTextField = React.createClass
</div>
_remove: (values) ->
_remove: (values) =>
field = @props.field
updates = {}
updates[field] = _.reject @props.participants[field], (p) ->
@ -83,7 +82,7 @@ ParticipantsTextField = React.createClass
false
@props.change(updates)
_add: (values) ->
_add: (values) =>
# If the input is a string, parse out email addresses and build
# an array of contact objects. For each email address wrapped in
# parentheses, look for a preceding name, if one exists.
@ -143,7 +142,7 @@ ParticipantsTextField = React.createClass
@props.change(updates)
""
_showContextMenu: (participant) ->
_showContextMenu: (participant) =>
remote = require('remote')
# Warning: Menu is already initialized as Menu.cjsx!
@ -154,7 +153,7 @@ ParticipantsTextField = React.createClass
menu = new MenuClass()
menu.append(new MenuItem(
label: "Copy #{participant.email}"
click: -> require('clipboard').writeText(participant.email)
click: => require('clipboard').writeText(participant.email)
))
menu.append(new MenuItem(
type: 'separator'
@ -165,3 +164,5 @@ ParticipantsTextField = React.createClass
))
menu.popup(remote.getCurrentWindow())
module.exports = ParticipantsTextField

View file

@ -4,11 +4,10 @@ _ = require "underscore-plus"
{Spinner, EventedIFrame} = require 'ui-components'
FileFrameStore = require './file-frame-store'
module.exports =
FileFrame = React.createClass
displayName: 'FileFrame'
class FileFrame extends React.Component
@displayName: 'FileFrame'
render: ->
render: =>
src = if @state.ready then @state.filepath else ''
if @state.file
<div className="file-frame-container">
@ -18,20 +17,23 @@ FileFrame = React.createClass
else
<div></div>
getInitialState: ->
@getStateFromStores()
constructor: (@props) ->
@state = @getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@_unsubscribers = []
@_unsubscribers.push FileFrameStore.listen @_onChange
componentWillUnmount: ->
componentWillUnmount: =>
unsubscribe() for unsubscribe in @_unsubscribers
getStateFromStores: ->
getStateFromStores: =>
file: FileFrameStore.file()
filepath: FileDownloadStore.pathForFile(FileFrameStore.file())
ready: FileFrameStore.ready()
_onChange: ->
_onChange: =>
@setState(@getStateFromStores())
module.exports = FileFrame

View file

@ -6,11 +6,10 @@ React = require 'react'
ComponentRegistry} = require 'inbox-exports'
FileListStore = require './file-list-store'
module.exports =
FileList = React.createClass
displayName: 'FileList'
class FileList extends React.Component
@displayName: 'FileList'
componentWillMount: ->
componentWillMount: =>
prettySize = (size) ->
units = ['GB', 'MB', 'KB', 'bytes']
while size > 1024
@ -23,18 +22,18 @@ FileList = React.createClass
c1 = new ListTabular.Column
name: "Name"
flex: 1
resolver: (file) ->
resolver: (file) =>
<div>{file.filename}</div>
c2 = new ListTabular.Column
name: "Size"
width: '100px'
resolver: (file) ->
resolver: (file) =>
<div>{prettySize(file.size)}</div>
@columns = [c1, c2]
render: ->
render: =>
<MultiselectList
dataStore={FileListStore}
columns={@columns}
@ -44,4 +43,7 @@ FileList = React.createClass
className="file-list"
collection="file" />
_onDoubleClick: (item) ->
_onDoubleClick: (item) =>
module.exports = FileList

View file

@ -2,11 +2,13 @@ React = require "react/addons"
FileListStore = require './file-list-store'
{MultiselectActionBar} = require 'ui-components'
module.exports =
FileSelectionBar = React.createClass
displayName: 'FileSelectionBar'
class FileSelectionBar extends React.Component
@displayName: 'FileSelectionBar'
render: ->
render: =>
<MultiselectActionBar
dataStore={FileListStore}
collection="file" />
module.exports = FileSelectionBar

View file

@ -1,10 +1,9 @@
React = require 'react/addons'
module.exports =
ActivityBarCurlItem = React.createClass
displayName: 'ActivityBarCurlItem'
class ActivityBarCurlItem extends React.Component
@displayName: 'ActivityBarCurlItem'
render: ->
render: =>
<div className={"item status-code-#{@props.item.statusCode}"}>
<div className="code">{@props.item.statusCode}</div>
<a onClick={@_onRunCommand}>Run</a>
@ -12,11 +11,11 @@ ActivityBarCurlItem = React.createClass
{@props.item.command}
</div>
_onCopyCommand: ->
_onCopyCommand: =>
clipboard = require('clipboard')
clipboard.writeText(@props.item.command)
_onRunCommand: ->
_onRunCommand: =>
curlFile = "#{atom.getConfigDirPath()}/curl.command"
fs = require 'fs-plus'
if fs.existsSync(curlFile)
@ -25,3 +24,6 @@ ActivityBarCurlItem = React.createClass
fs.chmodSync(curlFile, '777')
shell = require 'shell'
shell.openItem(curlFile)
module.exports = ActivityBarCurlItem

View file

@ -2,14 +2,13 @@ React = require 'react/addons'
moment = require 'moment'
module.exports =
ActivityBarLongPollItem = React.createClass
displayName: 'ActivityBarLongPollItem'
class ActivityBarLongPollItem extends React.Component
@displayName: 'ActivityBarLongPollItem'
getInitialState: ->
expanded: false
constructor: (@props) ->
@state = expanded: false
render: ->
render: =>
if @state.expanded
payload = JSON.stringify(@props.item)
else
@ -29,3 +28,6 @@ ActivityBarLongPollItem = React.createClass
</div>
</div>
module.exports = ActivityBarLongPollItem

View file

@ -1,14 +1,13 @@
React = require 'react/addons'
classNames = require 'classnames'
module.exports =
ActivityBarTask = React.createClass
displayName: 'ActivityBarTask'
class ActivityBarTask extends React.Component
@displayName: 'ActivityBarTask'
getInitialState: ->
expanded: false
constructor: (@props) ->
@state = expanded: false
render: ->
render: =>
<div className={@_classNames()} onClick={=> @setState expanded: not @state.expanded}>
<div className="task-summary">
{@_taskSummary()}
@ -18,7 +17,7 @@ ActivityBarTask = React.createClass
</div>
</div>
_taskSummary: ->
_taskSummary: =>
qs = @props.task.queueState
errType = ""
errCode = ""
@ -35,7 +34,7 @@ ActivityBarTask = React.createClass
return "#{@props.task.constructor.name} #{errType} #{errCode} #{errMessage}"
_classNames: ->
_classNames: =>
qs = @props.task.queueState ? {}
classNames
"task": true
@ -46,3 +45,6 @@ ActivityBarTask = React.createClass
"task-remote-error": qs.remoteError
"task-is-processing": qs.isProcessing
"task-success": qs.performedLocal and qs.performedRemote
module.exports = ActivityBarTask

View file

@ -16,25 +16,25 @@ ActivityBarLongPollItem = require './activity-bar-long-poll-item'
ActivityBarClosedHeight = 30
module.exports =
ActivityBar = React.createClass
class ActivityBar extends React.Component
@displayName: "ActivityBar"
getInitialState: ->
_.extend @_getStateFromStores(),
constructor: (@props) ->
@state = _.extend @_getStateFromStores(),
height: ActivityBarClosedHeight
section: 'curl'
filter: ''
componentDidMount: ->
componentDidMount: =>
ipc.on 'report-issue', => @_onFeedback()
@taskQueueUnsubscribe = TaskQueue.listen @_onChange
@activityStoreUnsubscribe = ActivityBarStore.listen @_onChange
componentWillUnmount: ->
componentWillUnmount: =>
@taskQueueUnsubscribe() if @taskQueueUnsubscribe
@activityStoreUnsubscribe() if @activityStoreUnsubscribe
render: ->
render: =>
return <div></div> unless @state.visible
<ResizableRegion className="activity-bar"
@ -73,13 +73,13 @@ ActivityBar = React.createClass
</div>
</ResizableRegion>
_caret: ->
_caret: =>
if @state.height > ActivityBarClosedHeight
<i className="fa fa-caret-square-o-down" onClick={@_onHide}></i>
else
<i className="fa fa-caret-square-o-up" onClick={@_onShow}></i>
_sectionContent: ->
_sectionContent: =>
expandedDiv = <div></div>
matchingFilter = (item) =>
@ -124,33 +124,33 @@ ActivityBar = React.createClass
expandedDiv
_onChange: ->
_onChange: =>
@setState(@_getStateFromStores())
_onClear: ->
_onClear: =>
Actions.clearDeveloperConsole()
_onFilter: (ev) ->
_onFilter: (ev) =>
@setState(filter: ev.target.value)
_onDequeueAll: ->
_onDequeueAll: =>
Actions.dequeueAllTasks()
_onHide: ->
_onHide: =>
@setState
height: ActivityBarClosedHeight
_onShow: ->
_onShow: =>
@setState(height: 200) if @state.height < 100
_onExpandSection: (section) ->
_onExpandSection: (section) =>
@setState(section: section)
@_onShow()
_onToggleRegions: ->
_onToggleRegions: =>
Actions.toggleComponentRegions()
_onFeedback: ->
_onFeedback: =>
user = NamespaceStore.current().name
debugData = JSON.stringify({
@ -197,10 +197,13 @@ ActivityBar = React.createClass
DatabaseStore.localIdForModel(draft).then (localId) ->
Actions.composePopoutDraft(localId)
_getStateFromStores: ->
_getStateFromStores: =>
visible: ActivityBarStore.visible()
queue: TaskQueue._queue
completed: TaskQueue._completed
curlHistory: ActivityBarStore.curlHistory()
longPollHistory: ActivityBarStore.longPollHistory()
longPollState: ActivityBarStore.longPollState()
module.exports = ActivityBar

View file

@ -2,8 +2,10 @@ React = require "react"
{Actions} = require 'inbox-exports'
crypto = require "crypto"
module.exports = React.createClass
render: ->
class ContactChip extends React.Component
@displayName: "ContactChip"
render: =>
className = "contact-chip"
if @props.clickable
className += " clickable"
@ -21,23 +23,26 @@ module.exports = React.createClass
<span className="contact-name">{@_getParticipantDisplay()}</span>
</span>
_onClick: ->
_onClick: =>
return unless @props.clickable
clipboard = require('clipboard')
clipboard.writeText(@props.participant.email)
Actions.postNotification({message: "Copied #{@props.participant.email} to clipboard", type: 'success'})
_getParticipantDisplay: ->
_getParticipantDisplay: =>
@props.participant.displayName()
shouldComponentUpdate: (newProps, newState) ->
shouldComponentUpdate: (newProps, newState) =>
(newProps.participant?.email != @props.participant?.email) ||
(newProps.participant?.name != @props.participant?.name)
componentWillMount: ->
componentWillMount: =>
email = @props.participant.email.toLowerCase()
@md5 = crypto.createHash('md5').update(email).digest('hex')
nameMD5 = crypto.createHash('md5').update(email + @props.participant.name).digest('hex')
n = Math.floor(parseInt(nameMD5.slice(0, 2), 16) * 360/256)
@bg = "hsl(#{n}, 50%, 50%)"
module.exports = ContactChip

View file

@ -12,8 +12,10 @@ _ = require "underscore-plus"
# - 'primary'
# - 'list'
module.exports = React.createClass
render: ->
class Participants extends React.Component
@displayName: "Participants"
render: =>
chips = @getParticipants().map (p) =>
<InjectedComponent name="ContactChip"
key={p.id}
@ -25,7 +27,7 @@ module.exports = React.createClass
{chips}
</div>
getParticipants: ->
getParticipants: =>
myEmail = NamespaceStore.current().emailAddress
list = @props.participants
@ -38,5 +40,8 @@ module.exports = React.createClass
list
shouldComponentUpdate: (newProps, newState) ->
shouldComponentUpdate: (newProps, newState) =>
!_.isEqual(newProps.participants, @props.participants)
module.exports = Participants

View file

@ -2,11 +2,10 @@ _ = require 'underscore-plus'
React = require "react"
classNames = require 'classnames'
module.exports =
MessageParticipants = React.createClass
displayName: 'MessageParticipants'
class MessageParticipants extends React.Component
@displayName: 'MessageParticipants'
render: ->
render: =>
classSet = classNames
"participants": true
"message-participants": true
@ -16,7 +15,7 @@ MessageParticipants = React.createClass
{if @props.isDetailed then @_renderExpanded() else @_renderCollapsed()}
</div>
_renderCollapsed: ->
_renderCollapsed: =>
<span className="collapsed-participants">
<span className="participant-name from-contact">{@_shortNames(@props.from)}</span>
<span className="participant-label to-label">To:&nbsp;</span>
@ -31,7 +30,7 @@ MessageParticipants = React.createClass
</span>
</span>
_renderExpanded: ->
_renderExpanded: =>
<div className="expanded-participants">
<div className="participant-type">
<div className="participant-name from-contact">{@_fullContact(@props.from)}</div>
@ -56,10 +55,10 @@ MessageParticipants = React.createClass
</div>
_shortNames: (contacts=[]) ->
_shortNames: (contacts=[]) =>
_.map(contacts, (c) -> c.displayFirstName()).join(", ")
_fullContact: (contacts=[]) ->
_fullContact: (contacts=[]) =>
if contacts.length is 0
# This is necessary to make the floats work properly
<div>&nbsp;</div>
@ -80,19 +79,19 @@ MessageParticipants = React.createClass
</div>
)
_selectPlainText: (e) ->
_selectPlainText: (e) =>
textNode = e.currentTarget.childNodes[0]
@_selectText(textNode)
_selectCommaText: (e) ->
_selectCommaText: (e) =>
textNode = e.currentTarget.childNodes[0].childNodes[0]
@_selectText(textNode)
_selectBracketedText: (e) ->
_selectBracketedText: (e) =>
textNode = e.currentTarget.childNodes[1].childNodes[0] # because of React rendering
@_selectText(textNode)
_selectText: (textNode) ->
_selectText: (textNode) =>
range = document.createRange()
range.setStart(textNode, 0)
range.setEnd(textNode, textNode.length)
@ -100,3 +99,6 @@ MessageParticipants = React.createClass
selection.removeAllRanges()
selection.addRange(range)
module.exports = MessageParticipants

View file

@ -3,26 +3,25 @@ moment = require 'moment-timezone'
React = require 'react'
{Utils} = require 'inbox-exports'
module.exports =
MessageTimestamp = React.createClass
displayName: 'MessageTimestamp'
propTypes:
class MessageTimestamp extends React.Component
@displayName: 'MessageTimestamp'
@propTypes:
date: React.PropTypes.object.isRequired,
className: React.PropTypes.string,
isDetailed: React.PropTypes.bool
onClick: React.PropTypes.func
shouldComponentUpdate: (nextProps, nextState) ->
shouldComponentUpdate: (nextProps, nextState) =>
+nextProps.date isnt +@props.date or nextProps.isDetailed isnt @props.isDetailed
render: ->
render: =>
<div className={@props.className}
onClick={@props.onClick}>{@_formattedDate()}</div>
_formattedDate: ->
_formattedDate: =>
moment.tz(@props.date, Utils.timeZone).format(@_timeFormat())
_timeFormat: ->
_timeFormat: =>
if @props.isDetailed
return "DD / MM / YYYY h:mm a z"
else
@ -44,3 +43,6 @@ MessageTimestamp = React.createClass
_today: -> moment.tz(Utils.timeZone)
module.exports = MessageTimestamp

View file

@ -7,54 +7,62 @@ classNames = require 'classnames'
# Note: These always have a thread, but only sometimes get a
# message, depending on where in the UI they are being displayed.
ReplyButton = React.createClass
render: ->
class ReplyButton extends React.Component
@displayName: "ReplyButton"
render: =>
<button className="btn btn-toolbar"
data-tooltip="Reply"
onClick={@_onReply}>
<RetinaImg name="toolbar-reply.png" />
</button>
_onReply: (e) ->
_onReply: (e) =>
return unless Utils.nodeIsVisible(e.currentTarget)
Actions.composeReply(threadId: FocusedContentStore.focusedId('thread'))
e.stopPropagation()
ReplyAllButton = React.createClass
render: ->
class ReplyAllButton extends React.Component
@displayName: "ReplyAllButton"
render: =>
<button className="btn btn-toolbar"
data-tooltip="Reply All"
onClick={@_onReplyAll}>
<RetinaImg name="toolbar-reply-all.png" />
</button>
_onReplyAll: (e) ->
_onReplyAll: (e) =>
return unless Utils.nodeIsVisible(e.currentTarget)
Actions.composeReplyAll(threadId: FocusedContentStore.focusedId('thread'))
e.stopPropagation()
ForwardButton = React.createClass
render: ->
class ForwardButton extends React.Component
@displayName: "ForwardButton"
render: =>
<button className="btn btn-toolbar"
data-tooltip="Forward"
onClick={@_onForward}>
<RetinaImg name="toolbar-forward.png" />
</button>
_onForward: (e) ->
_onForward: (e) =>
return unless Utils.nodeIsVisible(e.currentTarget)
Actions.composeForward(threadId: FocusedContentStore.focusedId('thread'))
e.stopPropagation()
ArchiveButton = React.createClass
render: ->
class ArchiveButton extends React.Component
@displayName: "ArchiveButton"
render: =>
<button className="btn btn-toolbar btn-archive"
data-tooltip="Archive"
onClick={@_onArchive}>
<RetinaImg name="toolbar-archive.png" />
</button>
_onArchive: (e) ->
_onArchive: (e) =>
return unless Utils.nodeIsVisible(e.currentTarget)
Actions.archive()
e.stopPropagation()

View file

@ -4,21 +4,21 @@ TemplateStore = require './template-store'
{Actions, Message, DatabaseStore} = require 'inbox-exports'
{Popover, Menu, RetinaImg} = require 'ui-components'
module.exports =
TemplatePicker = React.createClass
displayName: 'TemplatePicker'
class TemplatePicker extends React.Component
@displayName: 'TemplatePicker'
getInitialState: ->
searchValue: ""
templates: TemplateStore.items()
constructor: (@props) ->
@state =
searchValue: ""
templates: TemplateStore.items()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = TemplateStore.listen @_onStoreChange
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe() if @unsubscribe
render: ->
render: =>
button = <button className="btn btn-toolbar">
<RetinaImg name="toolbar-templates.png"/>
<RetinaImg name="toolbar-chevron.png"/>
@ -49,7 +49,7 @@ TemplatePicker = React.createClass
</Popover>
_filteredTemplates: (search) ->
_filteredTemplates: (search) =>
search ?= @state.searchValue
items = TemplateStore.items()
@ -58,23 +58,26 @@ TemplatePicker = React.createClass
_.filter items, (t) ->
t.name.toLowerCase().indexOf(search.toLowerCase()) == 0
_onStoreChange: ->
_onStoreChange: =>
@setState
templates: @_filteredTemplates()
_onSearchValueChange: ->
_onSearchValueChange: =>
newSearch = event.target.value
@setState
searchValue: newSearch
templates: @_filteredTemplates(newSearch)
_onChooseTemplate: (template) ->
_onChooseTemplate: (template) =>
Actions.insertTemplateId({templateId:template.id, draftLocalId: @props.draftLocalId})
@refs.popover.close()
_onManageTemplates: ->
_onManageTemplates: =>
Actions.showTemplates()
_onNewTemplate: ->
_onNewTemplate: =>
Actions.createTemplate({draftLocalId: @props.draftLocalId})
module.exports = TemplatePicker

View file

@ -2,26 +2,25 @@ _ = require 'underscore-plus'
React = require 'react'
{Actions, Message, DraftStore} = require 'inbox-exports'
module.exports =
TemplateStatusBar = React.createClass
displayName: 'TemplateStatusBar'
class TemplateStatusBar extends React.Component
@displayName: 'TemplateStatusBar'
propTypes:
@propTypes:
draftLocalId: React.PropTypes.string
getInitialState: ->
draft: null
constructor: (@props) ->
@state = draft: null
componentDidMount: ->
componentDidMount: =>
@_proxy = DraftStore.sessionForLocalId(@props.draftLocalId)
@unsubscribe = @_proxy.listen(@_onDraftChange, @)
if @_proxy.draft()
@_onDraftChange()
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe() if @unsubscribe
render: ->
render: =>
if @_draftUsesTemplate()
<div className="template-status-bar">
Press "tab" to quickly fill in the blanks - highlighting will not be visible to recipients.
@ -29,9 +28,12 @@ TemplateStatusBar = React.createClass
else
<div></div>
_onDraftChange: ->
_onDraftChange: =>
@setState(draft: @_proxy.draft())
_draftUsesTemplate: ->
_draftUsesTemplate: =>
return unless @state.draft
@state.draft.body.search(/<code[^>]*class="var[^>]*>/i) > 0
module.exports = TemplateStatusBar

View file

@ -10,20 +10,19 @@ _ = require "underscore-plus"
## THIS FILE IS NOT IN USE! DEPRECATED IN FAVOR OF ModeToggle
##
module.exports =
ModeSwitch = React.createClass
displayName: 'ModeSwitch'
class ModeSwitch extends React.Component
@displayName: 'ModeSwitch'
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = WorkspaceStore.listen @_onStateChanged
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe?()
render: ->
render: =>
return <div></div> unless @state.visible
knobX = if @state.mode is 'list' then 25 else 41
@ -54,22 +53,25 @@ ModeSwitch = React.createClass
style={paddingLeft:12} />
</div>
_onStateChanged: ->
_onStateChanged: =>
@setState(@_getStateFromStores())
_getStateFromStores: ->
_getStateFromStores: =>
rootModes = WorkspaceStore.rootSheet().supportedModes
rootVisible = WorkspaceStore.rootSheet() is WorkspaceStore.topSheet()
mode: WorkspaceStore.layoutMode()
visible: rootVisible and rootModes and rootModes.length > 1
_onToggleMode: ->
_onToggleMode: =>
if @state.mode is 'list'
Actions.selectLayoutMode('split')
else
Actions.selectLayoutMode('list')
_onSetMode: (event) ->
_onSetMode: (event) =>
Actions.selectLayoutMode(event.target.dataset.mode)
event.stopPropagation()
module.exports = ModeSwitch

View file

@ -5,20 +5,19 @@
React = require "react"
_ = require "underscore-plus"
module.exports =
ModeToggle = React.createClass
displayName: 'ModeToggle'
class ModeToggle extends React.Component
@displayName: 'ModeToggle'
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = WorkspaceStore.listen(@_onStateChanged, @)
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe?()
render: ->
render: =>
return <div></div> unless @state.visible
<div className="mode-toggle"
@ -30,18 +29,21 @@ ModeToggle = React.createClass
onClick={@_onToggleMode} />
</div>
_onStateChanged: ->
_onStateChanged: =>
@setState(@_getStateFromStores())
_getStateFromStores: ->
_getStateFromStores: =>
rootModes = WorkspaceStore.rootSheet().supportedModes
rootVisible = WorkspaceStore.rootSheet() is WorkspaceStore.topSheet()
mode: WorkspaceStore.layoutMode()
visible: rootVisible and rootModes and rootModes.length > 1
_onToggleMode: ->
_onToggleMode: =>
if @state.mode is 'list'
Actions.selectLayoutMode('split')
else
Actions.selectLayoutMode('list')
module.exports = ModeToggle

View file

@ -2,8 +2,10 @@ React = require 'react'
{Actions} = require 'inbox-exports'
NotificationStore = require './notifications-store'
NotificationStickyItem = React.createClass
render: ->
class NotificationStickyItem extends React.Component
@displayName: "NotificationStickyItem"
render: =>
notif = @props.notification
iconClass = if notif.icon then "fa #{notif.icon}" else ""
actionComponents = notif.actions?.map (action) =>
@ -13,39 +15,44 @@ NotificationStickyItem = React.createClass
<i className={iconClass}></i><span>{notif.message}</span>{actionComponents}
</div>
_fireItemAction: (notification, action) ->
_fireItemAction: (notification, action) =>
Actions.notificationActionTaken({notification, action})
module.exports =
NotificationStickyBar = React.createClass
class NotificationStickyBar extends React.Component
@displayName: "NotificationStickyBar"
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
_getStateFromStores: ->
_getStateFromStores: =>
items: NotificationStore.stickyNotifications()
_onDataChanged: ->
_onDataChanged: =>
@setState @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@_unlistener = NotificationStore.listen(@_onDataChanged, @)
@
# It's important that every React class explicitly stops listening to
# atom events before it unmounts. Thank you event-kit
# This can be fixed via a Reflux mixin
componentWillUnmount: ->
componentWillUnmount: =>
@_unlistener() if @_unlistener
@
render: ->
render: =>
<div className="notifications-sticky">
{@_notificationComponents()}
</div>
_notificationComponents: ->
_notificationComponents: =>
@state.items.map (notif) ->
<NotificationStickyItem notification={notif} />
module.exports = NotificationStickyBar

View file

@ -1,30 +1,33 @@
React = require 'react'
NotificationStore = require './notifications-store'
module.exports =
Notifications = React.createClass
class Notifications extends React.Component
@displayName: "Notifications"
getInitialState: ->
notifications: NotificationStore.notifications()
constructor: (@props) ->
@state = notifications: NotificationStore.notifications()
componentDidMount: ->
componentDidMount: =>
@unsubscribeStore = NotificationStore.listen @_onStoreChange
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribeStore() if @unsubscribeStore
render: ->
render: =>
<div className="notifications-momentary">
{@_notificationComponents()}
</div>
_notificationComponents: ->
_notificationComponents: =>
@state.notifications.map (notification) ->
<div key={notification.id}
className={"notification-item notification-#{notification.type}"}>
{notification.message}
</div>
_onStoreChange: ->
_onStoreChange: =>
@setState
notifications: NotificationStore.notifications()
module.exports = Notifications

View file

@ -4,19 +4,14 @@ React = require "react"
{Actions} = require 'inbox-exports'
{RetinaImg} = require 'ui-components'
module.exports =
SidebarFullContactDetails = React.createClass
class SidebarFullContactDetails extends React.Component
@displayName: "SidebarFullContactDetails"
_supportedProfileTypes:
twitter: true
linkedin: true
facebook: true
propTypes:
@propTypes:
contact: React.PropTypes.object
fullContact: React.PropTypes.object
render: ->
render: =>
<div className="full-contact">
<div className="header">
{@_profilePhoto()}
@ -34,7 +29,7 @@ SidebarFullContactDetails = React.createClass
{@_noInfo()}
</div>
_socialProfiles: ->
_socialProfiles: =>
profiles = @_profiles()
return profiles.map (profile) =>
<div className="social-profile">
@ -45,14 +40,19 @@ SidebarFullContactDetails = React.createClass
</div>
</div>
_profiles: ->
_profiles: =>
profiles = @props.fullContact.socialProfiles ? []
profiles = _.filter profiles, (p) => @_supportedProfileTypes[p.typeId]
_showSocialProfiles: ->
_supportedProfileTypes:
twitter: true
linkedin: true
facebook: true
_showSocialProfiles: =>
@_profiles().length > 0
_username: (profile) ->
_username: (profile) =>
if (profile.username ? "").length > 0
if profile.typeId is "twitter"
return "@#{profile.username}"
@ -61,12 +61,12 @@ SidebarFullContactDetails = React.createClass
else
return profile.typeName
_noInfo: ->
_noInfo: =>
if not @_showSocialProfiles() and not @_showSubheader()
<div className="sidebar-no-info">No additional information available.</div>
else return ""
_twitterBio: (profile) ->
_twitterBio: (profile) =>
return "" unless profile.typeId is "twitter"
return "" unless profile.bio?.length > 0
@ -77,13 +77,13 @@ SidebarFullContactDetails = React.createClass
<div className="bio sidebar-extra-info"
dangerouslySetInnerHTML={{__html: bio}}></div>
_showSubheader: ->
_showSubheader: =>
@_title().length > 0 or @_company().length > 0
_name: ->
_name: =>
(@props.fullContact.contactInfo?.fullName) ? @props.contact?.name
_title: ->
_title: =>
org = @_primaryOrg()
return "" unless org?
if org.current and org.title?
@ -92,7 +92,7 @@ SidebarFullContactDetails = React.createClass
return "Former #{org.title}"
else return ""
_company: ->
_company: =>
location = @props.fullContact.demographics?.locationGeneral ? ""
name = @_primaryOrg()?.name ? ""
if name.length > 0 and location.length > 0
@ -103,13 +103,13 @@ SidebarFullContactDetails = React.createClass
return "(#{location})"
else return ""
_primaryOrg: ->
_primaryOrg: =>
orgs = @props.fullContact.organizations ? []
org = _.findWhere orgs, isPrimary: true
if not org? then org = orgs[0]
return org
_profilePhoto: ->
_profilePhoto: =>
photos = @props.fullContact.photos ? []
photo = _.findWhere photo, isPrimary: true
if not photo? then photo = _.findWhere photo, typeId: "linkedin"
@ -117,3 +117,6 @@ SidebarFullContactDetails = React.createClass
if photo? and photo.url?
return <img src={photo.url} className="profile-photo" />
else return ""
module.exports = SidebarFullContactDetails

View file

@ -4,36 +4,39 @@ FullContactStore = require "./fullcontact-store"
SidebarFullContactDetails = require "./sidebar-fullcontact-details"
module.exports =
SidebarFullContact = React.createClass
class SidebarFullContact extends React.Component
@displayName: "SidebarFullContact"
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = FullContactStore.listen @_onChange
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe()
render: ->
render: =>
<div className="full-contact-sidebar">
<SidebarFullContactDetails contact={@state.focusedContact ? {}}
fullContact={@_fullContact()}/>
</div>
_fullContact: ->
_fullContact: =>
if @state.focusedContact?.email
return @state.fullContactCache[@state.focusedContact.email] ? {}
else
return {}
_onChange: ->
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: ->
_getStateFromStores: =>
fullContactCache: FullContactStore.fullContactCache()
focusedContact: FullContactStore.focusedContact()
SidebarFullContact.maxWidth = 300
SidebarFullContact.minWidth = 200
module.exports = SidebarFullContact

View file

@ -27,19 +27,19 @@ AccountKeys =
"sync_start_time": "Sync Start Time"
"sync_type": "Sync Type"
module.exports =
SidebarInternal = React.createClass
class SidebarInternal extends React.Component
@displayName: "SidebarInternal"
getInitialState: ->
@_getStateFromStores()
constructor: (@props) ->
@state = @_getStateFromStores()
componentDidMount: ->
componentDidMount: =>
@unsubscribe = InternalAdminStore.listen @_onChange
componentWillUnmount: ->
componentWillUnmount: =>
@unsubscribe()
render: ->
render: =>
return <div></div> unless @state.enabled
<div className="internal-sidebar">
@ -55,7 +55,7 @@ SidebarInternal = React.createClass
</div>
</div>
_renderAccount: ->
_renderAccount: =>
if @state.error
return <div>{@_errorString()}</div>
else if @state.data.loading
@ -70,7 +70,7 @@ SidebarInternal = React.createClass
else
<div>No Matching Account</div>
_renderApplications: ->
_renderApplications: =>
if @state.error
return <div>{@_errorString()}</div>
else if @state.data.loading
@ -84,16 +84,16 @@ SidebarInternal = React.createClass
else
<div>No Matching Applications</div>
_errorString: ->
_errorString: =>
if @state.error.toString().indexOf('ENOTFOUND') >= 0
"Unable to reach admin.nilas.com"
else
@state.error.toString()
_accountUrl: (account) ->
_accountUrl: (account) =>
"https://admin.inboxapp.com/accounts/#{account.id}"
_accountDetails: (account) ->
_accountDetails: (account) =>
cjsx = []
for key, value of account
displayName = AccountKeys[key]
@ -105,19 +105,22 @@ SidebarInternal = React.createClass
cjsx.push <div style={textAlign:'right'}><span style={float:'left'}>{displayName}:</span>{value}</div>
cjsx
_appUrl: (app) ->
_appUrl: (app) =>
"https://admin.inboxapp.com/apps/#{app.id}"
_appDetails: (app) ->
_appDetails: (app) =>
"No Extra Details"
_onChange: ->
_onChange: =>
@setState(@_getStateFromStores())
_getStateFromStores: ->
_getStateFromStores: =>
data: InternalAdminStore.dataForFocusedContact()
enabled: InternalAdminStore.enabled()
error: InternalAdminStore.error()
SidebarInternal.maxWidth = 300
SidebarInternal.minWidth = 200
module.exports = SidebarInternal

View file

@ -7,11 +7,10 @@ React = require 'react'
ComponentRegistry} = require 'inbox-exports'
DraftListStore = require './draft-list-store'
module.exports =
DraftList = React.createClass
displayName: 'DraftList'
class DraftList extends React.Component
@displayName: 'DraftList'
componentWillMount: ->
componentWillMount: =>
snippet = (html) =>
@draftSanitizer ?= document.createElement('div')
@draftSanitizer.innerHTML = html
@ -21,7 +20,7 @@ DraftList = React.createClass
c1 = new ListTabular.Column
name: "Name"
width: 200
resolver: (draft) ->
resolver: (draft) =>
Participants = ComponentRegistry.findViewByName('Participants')
return <div></div> unless Participants
<div className="participants">
@ -32,7 +31,7 @@ DraftList = React.createClass
c2 = new ListTabular.Column
name: "Message"
flex: 4
resolver: (draft) ->
resolver: (draft) =>
attachments = []
if draft.files?.length > 0
attachments = <div className="thread-icon thread-icon-attachment"></div>
@ -45,14 +44,14 @@ DraftList = React.createClass
c3 = new ListTabular.Column
name: "Date"
flex: 1
resolver: (draft) ->
resolver: (draft) =>
<span className="timestamp">{timestamp(draft.date)}</span>
@columns = [c1, c2, c3]
@commands =
'core:remove-item': @_onDelete
render: ->
render: =>
<MultiselectList
dataStore={DraftListStore}
columns={@columns}
@ -62,15 +61,18 @@ DraftList = React.createClass
className="draft-list"
collection="draft" />
_onDoubleClick: (item) ->
_onDoubleClick: (item) =>
DatabaseStore.localIdForModel(item).then (localId) ->
Actions.composePopoutDraft(localId)
# Additional Commands
_onDelete: ({focusedId}) ->
_onDelete: ({focusedId}) =>
item = @state.dataView.getById(focusedId)
return unless item
DatabaseStore.localIdForModel(item).then (localId) ->
Actions.destroyDraft(localId)
@_onShiftSelectedIndex(-1)
module.exports = DraftList

View file

@ -2,12 +2,14 @@ React = require "react/addons"
DraftListStore = require './draft-list-store'
{MultiselectActionBar} = require 'ui-components'
module.exports =
DraftSelectionBar = React.createClass
displayName: 'DraftSelectionBar'
class DraftSelectionBar extends React.Component
@displayName: 'DraftSelectionBar'
render: ->
render: =>
<MultiselectActionBar
dataStore={DraftListStore}
className="draft-list"
collection="draft" />
module.exports = DraftSelectionBar

View file

@ -2,14 +2,13 @@ React = require 'react'
_ = require 'underscore-plus'
{NamespaceStore} = require 'inbox-exports'
module.exports =
ThreadListParticipants = React.createClass
displayName: 'ThreadListParticipants'
class ThreadListParticipants extends React.Component
@displayName: 'ThreadListParticipants'
propTypes:
@propTypes:
thread: React.PropTypes.object.isRequired
render: ->
render: =>
items = @getParticipants()
spans = []
@ -55,7 +54,7 @@ ThreadListParticipants = React.createClass
{spans}
</div>
getParticipants: ->
getParticipants: =>
if @props.thread.metadata
list = []
last = null
@ -101,3 +100,6 @@ ThreadListParticipants = React.createClass
list
module.exports = ThreadListParticipants

View file

@ -12,11 +12,10 @@ classNames = require 'classnames'
ThreadListParticipants = require './thread-list-participants'
ThreadListStore = require './thread-list-store'
module.exports =
ThreadList = React.createClass
displayName: 'ThreadList'
class ThreadList extends React.Component
@displayName: 'ThreadList'
componentWillMount: ->
componentWillMount: =>
labelComponents = (thread) =>
for label in @state.threadLabelComponents
LabelComponent = label.view
@ -43,19 +42,19 @@ ThreadList = React.createClass
c1 = new ListTabular.Column
name: "★"
resolver: (thread) ->
resolver: (thread) =>
<div className="thread-icon thread-icon-#{lastMessageType(thread)}"></div>
c2 = new ListTabular.Column
name: "Name"
width: 200
resolver: (thread) ->
resolver: (thread) =>
<ThreadListParticipants thread={thread} />
c3 = new ListTabular.Column
name: "Message"
flex: 4
resolver: (thread) ->
resolver: (thread) =>
attachments = []
if thread.hasTagId('attachment')
attachments = <div className="thread-icon thread-icon-attachment"></div>
@ -67,7 +66,7 @@ ThreadList = React.createClass
c4 = new ListTabular.Column
name: "Date"
resolver: (thread) ->
resolver: (thread) =>
<span className="timestamp">{timestamp(thread.lastMessageTimestamp)}</span>
@columns = [c1, c2, c3, c4]
@ -82,7 +81,7 @@ ThreadList = React.createClass
className: classNames
'unread': item.isUnread()
render: ->
render: =>
<MultiselectList
dataStore={ThreadListStore}
columns={@columns}
@ -93,28 +92,31 @@ ThreadList = React.createClass
# Additional Commands
_onArchive: ->
_onArchive: =>
if @_viewingFocusedThread() or ThreadListStore.view().selection.count() is 0
Actions.archive()
else
Actions.archiveSelection()
_onReply: ({focusedId}) ->
_onReply: ({focusedId}) =>
return unless focusedId? and @_viewingFocusedThread()
Actions.composeReply(threadId: focusedId)
_onReplyAll: ({focusedId}) ->
_onReplyAll: ({focusedId}) =>
return unless focusedId? and @_viewingFocusedThread()
Actions.composeReplyAll(threadId: focusedId)
_onForward: ({focusedId}) ->
_onForward: ({focusedId}) =>
return unless focusedId? and @_viewingFocusedThread()
Actions.composeForward(threadId: focusedId)
# Helpers
_viewingFocusedThread: ->
_viewingFocusedThread: =>
if WorkspaceStore.layoutMode() is "list"
WorkspaceStore.topSheet() is WorkspaceStore.Sheet.Thread
else
true
module.exports = ThreadList

View file

@ -2,12 +2,13 @@ React = require "react/addons"
ThreadListStore = require './thread-list-store'
{MultiselectActionBar} = require 'ui-components'
module.exports =
ThreadSelectionBar = React.createClass
displayName: 'ThreadSelectionBar'
class ThreadSelectionBar extends React.Component
@displayName: 'ThreadSelectionBar'
render: ->
render: =>
<MultiselectActionBar
dataStore={ThreadListStore}
className="thread-list"
collection="thread" />
module.exports = ThreadSelectionBar

View file

@ -298,141 +298,3 @@ describe "ThreadList", ->
advanceClock(100)
items = ReactTestUtils.scryRenderedComponentsWithType(@thread_list, ListTabular.Item)
expect(items.length).toBe(test_threads().length)
# describe "ThreadListNarrow", ->
# beforeEach ->
# InboxTestUtils.loadKeymap("internal_packages/thread-list/keymaps/thread-list.cson")
# spyOn(ThreadStore, "_onNamespaceChanged")
# spyOn(DatabaseStore, "findAll").andCallFake ->
# new Promise (resolve, reject) -> resolve(test_threads())
# ThreadStore._resetInstanceVars()
# ComponentRegistry.register
# name: 'Participants'
# view: ParticipantsItem
# @thread_list = ReactTestUtils.renderIntoDocument(
# <ThreadListNarrow />
# )
# it "renders into the document", ->
# expect(ReactTestUtils.isCompositeComponentWithType(@thread_list,
# ThreadListNarrow)).toBe true
# it "by default has zero children", ->
# items = ReactTestUtils.scryRenderedComponentsWithType(@thread_list,
# ThreadListNarrowItem)
# expect(items.length).toBe 0
# describe "Populated thread list", ->
# beforeEach ->
# ThreadStore._items = test_threads()
# ThreadStore._focusedId = null
# ThreadStore.trigger()
# @thread_list_node = React.findDOMNode(@thread_list)
# it "renders all of the thread list items", ->
# items = ReactTestUtils.scryRenderedComponentsWithType(@thread_list,
# ThreadListNarrowItem)
# expect(items.length).toBe 3
# describe "Shifting selected index", ->
# beforeEach ->
# spyOn(@thread_list, "_onShiftSelectedIndex")
# spyOn(Actions, "selectThreadId")
# it "can move selection up", ->
# atom.commands.dispatch(document.body, "core:previous-item")
# expect(@thread_list._onShiftSelectedIndex).toHaveBeenCalledWith(-1)
# it "can move selection down", ->
# atom.commands.dispatch(document.body, "core:next-item")
# expect(@thread_list._onShiftSelectedIndex).toHaveBeenCalledWith(1)
# describe "Triggering message list commands", ->
# beforeEach ->
# spyOn(Actions, "composeReply")
# spyOn(Actions, "composeReplyAll")
# spyOn(Actions, "composeForward")
# ThreadStore._onSelectThreadId("111")
# @thread = ThreadStore.selectedThread()
# spyOn(@thread, "archive")
# spyOn(@thread_list, "_onShiftSelectedIndex")
# spyOn(Actions, "selectThreadId")
# it "can reply to the currently selected thread", ->
# atom.commands.dispatch(document.body, "application:reply")
# expect(Actions.composeReply).toHaveBeenCalledWith(threadId: @thread.id)
# it "can reply all to the currently selected thread", ->
# atom.commands.dispatch(document.body, "application:reply-all")
# expect(Actions.composeReplyAll).toHaveBeenCalledWith(threadId: @thread.id)
# it "can forward the currently selected thread", ->
# atom.commands.dispatch(document.body, "application:forward")
# expect(Actions.composeForward).toHaveBeenCalledWith(threadId: @thread.id)
# it "can archive the currently selected thread", ->
# atom.commands.dispatch(document.body, "application:remove-item")
# expect(@thread.archive).toHaveBeenCalled()
# it "can archive the currently selected thread and navigate up", ->
# atom.commands.dispatch(document.body, "application:remove-and-previous")
# expect(@thread.archive).toHaveBeenCalled()
# expect(@thread_list._onShiftSelectedIndex).toHaveBeenCalledWith(-1)
# it "does nothing when no thread is selected", ->
# ThreadStore._focusedId = null
# atom.commands.dispatch(document.body, "application:reply")
# expect(Actions.composeReply.calls.length).toEqual(0)
# describe "ThreadListNarrowItem", ->
# beforeEach ->
# items = ReactTestUtils.scryRenderedComponentsWithType(@thread_list,
# ThreadListNarrowItem)
# item = items.filter (tli) -> tli.props.thread.id is "111"
# @thread_list_item = item[0]
# @thread_date = moment(@thread_list_item.props.thread.lastMessageTimestamp)
# it "finds the thread list item by id", ->
# expect(@thread_list_item.props.thread.id).toBe "111"
# it "fires the appropriate Action on click", ->
# spyOn(Actions, "selectThreadId")
# ReactTestUtils.Simulate.click React.findDOMNode(@thread_list_item)
# expect(Actions.focusThreadId).toHaveBeenCalledWith("111")
# it "sets the selected state on the thread item", ->
# ThreadStore._onSelectThreadId("111")
# items = ReactTestUtils.scryRenderedDOMComponentsWithClass(@thread_list, "selected")
# expect(items.length).toBe 1
# expect(items[0].props.id).toBe "111"
# it "renders de-selection when invalid id is emitted", ->
# ThreadStore._onSelectThreadId('abc')
# items = ReactTestUtils.scryRenderedDOMComponentsWithClass(@thread_list, "selected")
# expect(items.length).toBe 0
# # test "last_message_timestamp": 1415742036
# it "displays the time from threads LONG ago", ->
# spyOn(@thread_list_item, "_today").andCallFake =>
# @thread_date.add(2, 'years')
# expect(@thread_list_item._timeFormat()).toBe "MMM D YYYY"
# it "displays the time from threads a bit ago", ->
# spyOn(@thread_list_item, "_today").andCallFake =>
# @thread_date.add(2, 'days')
# expect(@thread_list_item._timeFormat()).toBe "MMM D"
# it "displays the time from threads exactly a day ago", ->
# spyOn(@thread_list_item, "_today").andCallFake =>
# @thread_date.add(1, 'day')
# expect(@thread_list_item._timeFormat()).toBe "h:mm a"
# it "displays the time from threads recently", ->
# spyOn(@thread_list_item, "_today").andCallFake =>
# @thread_date.add(2, 'hours')
# expect(@thread_list_item._timeFormat()).toBe "h:mm a"

View file

@ -11,19 +11,20 @@ Activate by adding a `data-tooltip="Label"` to any element
It's a global-level singleton
###
module.exports =
Tooltip = React.createClass
class Tooltip extends React.Component
@displayName: "Tooltip"
getInitialState: ->
top: 0
pos: "below"
left: 0
width: 0
pointerLeft: 0
display: false
content: ""
constructor: (@props) ->
@state =
top: 0
pos: "below"
left: 0
width: 0
pointerLeft: 0
display: false
content: ""
componentWillMount: ->
componentWillMount: =>
@CONTENT_PADDING = 15
@DEFAULT_DELAY = 1500
@KEEP_DELAY = 500
@ -31,17 +32,17 @@ Tooltip = React.createClass
@_showTimeout = null
@_showDelayTimeout = null
componentWillUnmount: ->
componentWillUnmount: =>
clearTimeout @_showTimeout
clearTimeout @_showDelayTimeout
render: ->
render: =>
<div className="tooltip-wrap #{@state.pos}" style={@_positionStyles()}>
<div className="tooltip-content">{@state.content}</div>
<div className="tooltip-pointer" style={left: @state.pointerLeft}></div>
</div>
_positionStyles: ->
_positionStyles: =>
top: @state.top
left: @state.left
width: @state.width
@ -49,25 +50,25 @@ Tooltip = React.createClass
# This are public methods so they can be bound to the window event
# listeners.
onMouseOver: (e) ->
onMouseOver: (e) =>
target = @_elementWithTooltip(e.target)
if target and Utils.nodeIsVisible(target) then @_onTooltipEnter(target)
else if @state.display then @_hideTooltip()
onMouseOut: (e) ->
onMouseOut: (e) =>
if @_elementWithTooltip(e.fromElement) and not @_elementWithTooltip(e.toElement)
@_onTooltipLeave()
onMouseDown: (e) ->
onMouseDown: (e) =>
if @state.display then @_hideTooltip()
_elementWithTooltip: (target) ->
_elementWithTooltip: (target) =>
while target
break if target?.dataset?.tooltip?
target = target.parentNode
return target
_onTooltipEnter: (target) ->
_onTooltipEnter: (target) =>
@_enteredTooltip = true
clearTimeout(@_showTimeout)
clearTimeout(@_showDelayTimeout)
@ -75,7 +76,7 @@ Tooltip = React.createClass
@_showTooltip(target)
, @_showDelay
_onTooltipLeave: ->
_onTooltipLeave: =>
return unless @_enteredTooltip
@_enteredTooltip = false
clearTimeout(@_showTimeout)
@ -87,8 +88,7 @@ Tooltip = React.createClass
@_showDelay = @DEFAULT_DELAY
, @KEEP_DELAY
_showTooltip: (target) ->
return unless @isMounted()
_showTooltip: (target) =>
return unless Utils.nodeIsVisible(target)
content = target.dataset.tooltip
guessedWidth = @_guessWidth(content)
@ -112,17 +112,17 @@ Tooltip = React.createClass
display: true
content: target.dataset.tooltip
_guessWidth: (content) ->
_guessWidth: (content) =>
# roughly 11px per character
guessWidth = content.length * 11
return Math.max(Math.min(guessWidth, 250), 50)
_tooltipLeft: (targetLeft, guessedWidth) ->
_tooltipLeft: (targetLeft, guessedWidth) =>
max = @_windowWidth() - guessedWidth - @CONTENT_PADDING
left = Math.min(Math.max(targetLeft - guessedWidth/2, @CONTENT_PADDING), max)
return left
_tooltipPointerLeft: (targetLeft, guessedWidth) ->
_tooltipPointerLeft: (targetLeft, guessedWidth) =>
POINTER_WIDTH = 6 + 2 #2px of border-radius
max = @_windowWidth() - @CONTENT_PADDING
min = @CONTENT_PADDING
@ -132,14 +132,13 @@ Tooltip = React.createClass
left = Math.max(Math.min(relativeLeft, guessedWidth-POINTER_WIDTH), POINTER_WIDTH)
return left
_windowWidth: ->
_windowWidth: =>
document.getElementsByTagName('body')[0].getBoundingClientRect().width
_windowHeight: ->
_windowHeight: =>
document.getElementsByTagName('body')[0].getBoundingClientRect().height
_hideTooltip: ->
return unless @isMounted()
_hideTooltip: =>
@setState
top: 0
left: 0
@ -147,3 +146,6 @@ Tooltip = React.createClass
pointerLeft: 0
display: false
content: ""
module.exports = Tooltip

View file

@ -1,4 +1,5 @@
_ = require 'underscore-plus'
classNames = require 'classnames'
React = require 'react'
{Utils} = require 'inbox-exports'
@ -13,33 +14,31 @@ idPropType = React.PropTypes.oneOfType([
# parents can lookup and change the data appropriately) and the new value.
# Either direct parents, grandparents, etc are responsible for updating
# the `value` prop to update the value again.
FormItem = React.createClass
displayName: "FormItem"
class FormItem extends React.Component
@displayName: "FormItem"
statics:
# A Set of valid types that can be sent into an "input" element
inputElementTypes:
"checkbox": true
"color": true
"date": true
"datetime": true
"datetime-local": true
"email": true
"file": true
"hidden": true
"month": true
"number": true
"password": true
"radio": true
"range": true
"search": true
"tel": true
"text": true
"time": true
"url": true
"week": true
@inputElementTypes:
"checkbox": true
"color": true
"date": true
"datetime": true
"datetime-local": true
"email": true
"file": true
"hidden": true
"month": true
"number": true
"password": true
"radio": true
"range": true
"search": true
"tel": true
"text": true
"time": true
"url": true
"week": true
propTypes:
@propTypes:
# Some sort of unique identifier
id: idPropType.isRequired
@ -94,8 +93,8 @@ FormItem = React.createClass
relationshipName: React.PropTypes.string
render: ->
classes = React.addons.classSet
render: =>
classes = classNames
"form-item": true
"valid": @state.valid
@ -120,19 +119,18 @@ FormItem = React.createClass
# DOM nodes we need to bend the React rules a bit and do a
# repeated-render until the `state` matches the validity state of the
# input.
componentWillMount: ->
componentWillMount: =>
@setState valid: true
componentDidMount: -> @refreshValidityState()
componentDidMount: => @refreshValidityState()
componentDidUpdate: -> @refreshValidityState()
componentDidUpdate: => @refreshValidityState()
shouldComponentUpdate: (nextProps, nextState) ->
shouldComponentUpdate: (nextProps, nextState) =>
not Utils.isEqualReact(nextProps, @props) or
not Utils.isEqualReact(nextState, @state)
refreshValidityState: -> _.defer =>
return unless @isMounted()
refreshValidityState: => _.defer =>
return unless @refs.input
el = React.findDOMNode(@refs.input)
@ -149,7 +147,7 @@ FormItem = React.createClass
@_lastValidity = newValidity
_renderError: ->
_renderError: =>
if @state.valid
<div></div>
else
@ -158,7 +156,7 @@ FormItem = React.createClass
else
<div></div>
_renderInput: ->
_renderInput: =>
inputProps = _.extend {}, @props,
ref: "input"
onChange: (eventOrValue) =>
@ -180,10 +178,10 @@ FormItem = React.createClass
else
console.warn "We do not support type #{@props.type} with attributes:", inputProps
GeneratedFieldset = React.createClass
displayName: "GeneratedFieldset"
class GeneratedFieldset extends React.Component
@displayName: "GeneratedFieldset"
propTypes:
@propTypes:
# Some sort of unique identifier
id: idPropType.isRequired
@ -205,7 +203,7 @@ GeneratedFieldset = React.createClass
heading: React.PropTypes.node
useHeading: React.PropTypes.bool
render: ->
render: =>
<fieldset>
{@_renderHeader()}
<div className="fieldset-form-items">
@ -214,20 +212,20 @@ GeneratedFieldset = React.createClass
{@_renderFooter()}
</fieldset>
shouldComponentUpdate: (nextProps, nextState) ->
shouldComponentUpdate: (nextProps, nextState) =>
not Utils.isEqualReact(nextProps, @props) or
not Utils.isEqualReact(nextState, @state)
refreshValidityStates: ->
refreshValidityStates: =>
for key, ref in @refs
ref.refreshValidityState() if key.indexOf("form-item") is 0
_renderHeader: ->
_renderHeader: =>
if @props.useHeading
<header><legend>{@props.heading}</legend></header>
else <div></div>
_renderFormItems: ->
_renderFormItems: =>
byRow = _.groupBy(@props.formItems, "row")
_.map byRow, (items=[], rowNum) =>
itemsWithSpacers = []
@ -255,7 +253,7 @@ GeneratedFieldset = React.createClass
# Given the raw data of an individual FormItem, prepare a set of props
# to pass down into the FormItem.
_propsFromFormItemData: (formItemData) ->
_propsFromFormItemData: (formItemData) =>
props = _.clone(formItemData)
props.key = props.id
error = @props.formItemErrors?[props.id]
@ -263,7 +261,7 @@ GeneratedFieldset = React.createClass
props.onChange = _.bind(@_onChangeItem, @)
return props
_onChangeItem: (itemId, newValue) ->
_onChangeItem: (itemId, newValue) =>
newFormItems = _.map @props.formItems, (formItem) ->
if formItem.id is itemId
newFormItem = _.clone(formItem)
@ -272,13 +270,13 @@ GeneratedFieldset = React.createClass
else return formItem
@props.onChange(@props.id, newFormItems)
_renderFooter: ->
_renderFooter: =>
<footer></footer>
GeneratedForm = React.createClass
displayName: "GeneratedForm"
class GeneratedForm extends React.Component
@displayName: "GeneratedForm"
propTypes:
@propTypes:
# Some sort of unique identifier
id: idPropType
@ -299,7 +297,7 @@ GeneratedForm = React.createClass
onSubmit: React.PropTypes.func.isRequired
render: ->
render: =>
<form className="generated-form" ref="form">
{@_renderHeaderFormError()}
{@_renderFieldsets()}
@ -309,34 +307,34 @@ GeneratedForm = React.createClass
</div>
</form>
shouldComponentUpdate: (nextProps, nextState) ->
shouldComponentUpdate: (nextProps, nextState) =>
not Utils.isEqualReact(nextProps, @props) or
not Utils.isEqualReact(nextState, @state)
_onSubmit: ->
_onSubmit: =>
valid = React.findDOMNode(@refs.form).reportValidity()
if valid
@props.onSubmit()
else
@refreshValidityStates()
refreshValidityStates: ->
refreshValidityStates: =>
for key, ref in @refs
ref.refreshValidityStates() if key.indexOf("fieldset") is 0
_renderHeaderFormError: ->
_renderHeaderFormError: =>
if @props.errors?.formError
<div className="form-error form-header-error">
{@props.errors.formError.message}
</div>
else return <div></div>
_renderFieldsets: ->
_renderFieldsets: =>
(@props.fieldsets ? []).map (fieldset) =>
props = @_propsFromFieldsetData(fieldset)
<GeneratedFieldset {...props} ref={"fieldset-#{fieldset.id}"} />
_propsFromFieldsetData: (fieldsetData) ->
_propsFromFieldsetData: (fieldsetData) =>
props = _.clone(fieldsetData)
errors = @props.errors?.formItemErrors
if errors then props.formItemErrors = errors
@ -344,7 +342,7 @@ GeneratedForm = React.createClass
props.onChange = _.bind(@_onChangeFieldset, @)
return props
_onChangeFieldset: (fieldsetId, newFormItems) ->
_onChangeFieldset: (fieldsetId, newFormItems) =>
newFieldsets = _.map @props.fieldsets, (fieldset) ->
if fieldset.id is fieldsetId
newFieldset = _.clone(fieldset)

View file

@ -1,43 +0,0 @@
_ = require 'underscore-plus'
React = require 'react/addons'
classNames = require 'classnames'
{ComponentRegistry} = require 'inbox-exports'
ThreadListItemMixin = require './thread-list-item-mixin'
DefaultParticipants = React.createClass
render: ->
<div className="participants">
{_.pluck(@props.participants, "email").join ", "}
</div>
module.exports =
ThreadListNarrowItem = React.createClass
mixins: [ComponentRegistry.Mixin, ThreadListItemMixin]
displayName: 'ThreadListNarrowItem'
components: ["Participants"]
render: ->
Participants = @state.Participants ? DefaultParticipants
<div className={@_containerClasses()} onClick={@_onClick} id={@props.thread.id}>
<div className="thread-title">
<span className="btn-icon star-button pull-right"
onClick={@_toggleStar}
><i className={"fa " + (@_isStarred() and 'fa-star' or 'fa-star-o')}/></span>
<div className="message-time">
{@threadTime()}
</div>
<Participants participants={@props.thread.participants} context={'list'} clickable={false}/>
</div>
<div className="preview-body">
<span className="subject">{@_subject()}</span>
<span className="snippet">{@_snippet()}</span>
</div>
</div>
_containerClasses: ->
classNames
'unread': @props.unread
'selected': @props.selected
'thread-list-item': true
'thread-list-narrow-item': true

View file

@ -1,23 +0,0 @@
_ = require 'underscore-plus'
React = require 'react'
ThreadListMixin = require './thread-list-mixin'
ThreadListNarrowItem = require './thread-list-narrow-item'
module.exports =
ThreadListNarrow = React.createClass
displayName: 'ThreadListMixin'
mixins: [ThreadListMixin]
render: ->
<div tabIndex="-1"
className="thread-list-container thread-list-narrow">
{@_threadComponents()}
</div>
_threadComponents: ->
@state.threads.map (thread) =>
<ThreadListNarrowItem key={thread.id}
thread={thread}
unread={thread.isUnread()}
selected={thread?.id == @state?.selected}/>

View file

@ -16,10 +16,10 @@ Generally, you wrap {MultiselectActionBar} in your own simple component to provi
and other settings:
```
ThreadSelectionBar = React.createClass
displayName: 'ThreadSelectionBar'
class MultiselectActionBar extends React.Component
@displayName: 'MultiselectActionBar'
render: ->
render: =>
<MultiselectActionBar
dataStore={ThreadListStore}
className="thread-list"
@ -128,4 +128,4 @@ class MultiselectActionBar extends React.Component
@state.view.selection.clear()
module.exports = MultiselectActionBar
module.exports = MultiselectActionBar

View file

@ -5,9 +5,10 @@ _ = require 'underscore-plus'
###
###
module.exports =
QuotedTextToggleButton = React.createClass
render: ->
class QuotedTextToggleButton extends React.Component
@displayName: "QuotedTextToggleButton"
render: =>
style =
'backgroundColor': '#f7f7f7'
'borderRadius': 5
@ -32,3 +33,4 @@ QuotedTextToggleButton = React.createClass
<a onClick={@props.onClick} style={style}>{content}</a>
module.exports = QuotedTextToggleButton

View file

@ -70,7 +70,7 @@ class Spinner extends React.Component
# If you don't want to make your own background for the loading state,
# this is a convenient default.
_renderDotsWithCover: =>
coverClasses = React.addons.classSet
coverClasses = classNames
"spinner-cover": true
"hidden": @state.hidden

View file

@ -76,7 +76,9 @@ class DraftStoreProxy
throw new Error("DraftChangeSet was modified before the draft was prepared.")
@_emitter.emit('trigger')
@prepare().catch (error) ->
console.log("DraftStoreProxy prepare() failed with error #{error.toString()}.")
console.error(error)
console.error(error.stack)
throw new Error("DraftStoreProxy prepare() failed with error #{error.toString()}.")
draft: ->
@changes.applyToModel(@_draft)

View file

@ -185,7 +185,8 @@ TaskQueue = Reflux.createStore
task.queueState.isProcessing = false
@_queue = queue
catch e
console.log("Queue deserialization failed with error: #{e.toString()}")
if not atom.inSpecMode()
console.log("Queue deserialization failed with error: #{e.toString()}")
_saveQueueToDisk: (callback) ->
queueFile = path.join(atom.getConfigDirPath(), 'task-queue.json')