mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-21 07:46:06 +08:00
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:
parent
57cb02c76a
commit
e3dfbe59be
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: </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> </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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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}/>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue