mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-09-04 11:44:47 +08:00
feat(messages): expandable message headers
Summary: feat(messages): expandable message headers Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1267
This commit is contained in:
parent
09dc8887b7
commit
b479e099c1
12 changed files with 264 additions and 146 deletions
|
@ -22,7 +22,7 @@ MessageItem = React.createClass
|
|||
# keyed by a fileId. The value is the downloadData.
|
||||
downloads: FileDownloadStore.downloadsForFileIds(@props.message.fileIds())
|
||||
showQuotedText: @_messageIsEmptyForward()
|
||||
collapsed: @props.collapsed
|
||||
detailedHeaders: false
|
||||
|
||||
componentDidMount: ->
|
||||
@_storeUnlisten = FileDownloadStore.listen(@_onDownloadStoreChange)
|
||||
|
@ -38,8 +38,13 @@ MessageItem = React.createClass
|
|||
attachments = <div className="attachments-area">{attachments}</div>
|
||||
|
||||
header =
|
||||
<header className="message-header" onClick={@_onToggleCollapsed}>
|
||||
<MessageTimestamp className="message-time" date={@props.message.date} />
|
||||
<header className="message-header">
|
||||
|
||||
<MessageTimestamp className="message-time"
|
||||
onClick={=> @setState detailedHeaders: true}
|
||||
isDetailed={@state.detailedHeaders}
|
||||
date={@props.message.date} />
|
||||
|
||||
<div className="message-actions">
|
||||
{<Action thread={@props.thread} message={@props.message} /> for Action in messageActions}
|
||||
</div>
|
||||
|
@ -47,27 +52,27 @@ MessageItem = React.createClass
|
|||
<MessageParticipants to={@props.message.to}
|
||||
cc={@props.message.cc}
|
||||
from={@props.message.from}
|
||||
onClick={=> @setState detailedHeaders: true}
|
||||
thread_participants={@props.thread_participants}
|
||||
detailedParticipants={@state.detailedHeaders}
|
||||
message_participants={@props.message.participants()} />
|
||||
|
||||
<div className="collapse-headers"
|
||||
style={if @state.detailedHeaders then {display: "block"} else {display: "none"}}
|
||||
onClick={=> @setState detailedHeaders: false}><i className="fa fa-chevron-up"></i>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
if @state.collapsed
|
||||
<div className="message-item-wrap collapsed">
|
||||
<div className="messsage-item-area">
|
||||
{header}
|
||||
</div>
|
||||
</div>
|
||||
else
|
||||
<div className="message-item-wrap">
|
||||
<div className="message-item-area">
|
||||
{header}
|
||||
{attachments}
|
||||
<EmailFrame showQuotedText={@state.showQuotedText}>
|
||||
{@_formatBody()}
|
||||
</EmailFrame>
|
||||
<a className={@_quotedTextClasses()} onClick={@_toggleQuotedText}></a>
|
||||
</div>
|
||||
<div className="message-item-wrap">
|
||||
<div className="message-item-area">
|
||||
{header}
|
||||
{attachments}
|
||||
<EmailFrame showQuotedText={@state.showQuotedText}>
|
||||
{@_formatBody()}
|
||||
</EmailFrame>
|
||||
<a className={@_quotedTextClasses()} onClick={@_toggleQuotedText}></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
_quotedTextClasses: -> React.addons.classSet
|
||||
"quoted-text-control": true
|
||||
|
@ -122,7 +127,3 @@ MessageItem = React.createClass
|
|||
_onDownloadStoreChange: ->
|
||||
@setState
|
||||
downloads: FileDownloadStore.downloadsForFileIds(@props.message.fileIds())
|
||||
|
||||
_onToggleCollapsed: ->
|
||||
@setState
|
||||
collapsed: !@state.collapsed
|
||||
|
|
|
@ -102,12 +102,6 @@ MessageList = React.createClass
|
|||
collapsed={collapsed}
|
||||
thread_participants={@_threadParticipants()} />
|
||||
|
||||
# Start collapsing messages if we've loaded more than 15. This prevents
|
||||
# us from trying to load an unbounded number of iframes until we have
|
||||
# a better optimized message list.
|
||||
if components.length > 10
|
||||
collapsed = true
|
||||
|
||||
components
|
||||
|
||||
_onChange: ->
|
||||
|
|
|
@ -6,39 +6,46 @@ MessageParticipants = React.createClass
|
|||
displayName: 'MessageParticipants'
|
||||
|
||||
render: ->
|
||||
<div className="participants message-participants">
|
||||
{@_formattedParticipants()}
|
||||
classSet = React.addons.classSet
|
||||
"participants": true
|
||||
"message-participants": true
|
||||
"collapsed": not @props.detailedParticipants
|
||||
|
||||
<div className={classSet} onClick={@props.onClick}>
|
||||
{if @props.detailedParticipants then @_renderExpanded() else @_renderCollapsed()}
|
||||
</div>
|
||||
|
||||
_formattedParticipants: ->
|
||||
<span>
|
||||
<span className="participant-label from-label">From:</span>
|
||||
<span className="participant-name from-contact">{@_joinNames(@props.from)}</span>
|
||||
{if @_isToEveryone() then @_toEveryone() else @_toSome()}
|
||||
</span>
|
||||
|
||||
_toEveryone: ->
|
||||
<span>
|
||||
_renderCollapsed: ->
|
||||
<span className="collapsed-participants">
|
||||
<span className="participant-name from-contact">{@_shortNames(@props.from)}</span>
|
||||
<span className="participant-label to-label"> > </span>
|
||||
<span className="participant-name to-everyone">Everyone</span>
|
||||
</span>
|
||||
|
||||
_toSome: ->
|
||||
if @props.cc.length > 0
|
||||
cc_spans = <span>
|
||||
<span className="participant-label cc-label">CC: </span>
|
||||
<span className="participant-name cc-contact">{@_joinNames(@props.cc)}</span>
|
||||
<span className="participant-name to-contact">{@_shortNames(@props.to)}</span>
|
||||
<span style={if @props.cc.length > 0 then display:"inline" else display:"none"}>
|
||||
<span className="participant-label cc-label">Cc: </span>
|
||||
<span className="participant-name cc-contact">{@_shortNames(@props.cc)}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span className="participant-label to-label"> > </span>
|
||||
<span className="participant-name to-contact">{@_joinNames(@props.to)}</span>
|
||||
{cc_spans}
|
||||
</span>
|
||||
|
||||
_joinNames: (contacts=[]) ->
|
||||
_renderExpanded: ->
|
||||
<div className="expanded-participants">
|
||||
<div>
|
||||
<div className="participant-label from-label">From: </div>
|
||||
<div className="participant-name from-contact">{@_fullContact(@props.from)}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="participant-label to-label">To: </div>
|
||||
<div className="participant-name to-contact">{@_fullContact(@props.to)}</div>
|
||||
</div>
|
||||
|
||||
<div style={if @props.cc.length > 0 then display:"inline" else display:"none"}>
|
||||
<div className="participant-label cc-label">Cc: </div>
|
||||
<div className="participant-name cc-contact">{@_fullContact(@props.cc)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
_shortNames: (contacts=[]) ->
|
||||
_.map(contacts, (c) -> c.displayFirstName()).join(", ")
|
||||
|
||||
_isToEveryone: ->
|
||||
mp = _.map(@props.message_participants, (c) -> c.email)
|
||||
tp = _.map(@props.thread_participants, (c) -> c.email)
|
||||
mp.length > 10 and _.difference(tp, mp).length is 0
|
||||
_fullContact: (contacts=[]) ->
|
||||
_.map(contacts, (c) -> c.displayFullContact()).join(", ")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
moment = require 'moment'
|
||||
moment = require 'moment-timezone'
|
||||
React = require 'react'
|
||||
|
||||
module.exports =
|
||||
|
@ -7,26 +7,37 @@ MessageTimestamp = React.createClass
|
|||
propTypes:
|
||||
date: React.PropTypes.object.isRequired,
|
||||
className: React.PropTypes.string,
|
||||
isDetailed: React.PropTypes.bool
|
||||
onClick: React.PropTypes.func
|
||||
|
||||
render: ->
|
||||
<div className={@props.className}>{moment(@props.date).format(@_timeFormat())}</div>
|
||||
<div className={@props.className}
|
||||
onClick={@props.onClick}>{@_formattedDate()}</div>
|
||||
|
||||
_formattedDate: ->
|
||||
moment.tz(@props.date, @_currentTimezone()).format(@_timeFormat())
|
||||
|
||||
_timeFormat: ->
|
||||
today = moment(@_today())
|
||||
dayOfEra = today.dayOfYear() + today.year() * 365
|
||||
msgDate = moment(@props.date)
|
||||
msgDayOfEra = msgDate.dayOfYear() + msgDate.year() * 365
|
||||
diff = dayOfEra - msgDayOfEra
|
||||
if diff < 1
|
||||
return "h:mm a"
|
||||
if diff < 4
|
||||
return "MMM D, h:mm a"
|
||||
else if diff > 1 and diff <= 365
|
||||
return "MMM D"
|
||||
if @props.isDetailed
|
||||
return "ddd, MMM Do YYYY, h:mm:ss a z"
|
||||
else
|
||||
return "MMM D YYYY"
|
||||
today = moment(@_today())
|
||||
dayOfEra = today.dayOfYear() + today.year() * 365
|
||||
msgDate = moment(@props.date)
|
||||
msgDayOfEra = msgDate.dayOfYear() + msgDate.year() * 365
|
||||
diff = dayOfEra - msgDayOfEra
|
||||
if diff < 1
|
||||
return "h:mm a"
|
||||
if diff < 4
|
||||
return "MMM D, h:mm a"
|
||||
else if diff > 1 and diff <= 365
|
||||
return "MMM D"
|
||||
else
|
||||
return "MMM D YYYY"
|
||||
|
||||
# Stubbable for testing. Returns a `moment`
|
||||
_today: -> moment()
|
||||
_today: -> moment.tz(@_currentTimezone())
|
||||
|
||||
_currentTimezone: -> Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ EmailFrameStub = React.createClass({render: -> <div></div>})
|
|||
MessageItem = proxyquire '../lib/message-item.cjsx',
|
||||
'./email-frame': EmailFrameStub
|
||||
|
||||
MessageTimestamp = require '../lib/message-timestamp.cjsx'
|
||||
|
||||
|
||||
describe "MessageItem", ->
|
||||
beforeEach ->
|
||||
|
@ -98,7 +100,7 @@ describe "MessageItem", ->
|
|||
namespaceId: "nsid"
|
||||
|
||||
@threadParticipants = [user_1, user_2, user_3, user_4]
|
||||
|
||||
|
||||
# Generate the test component. Should be called after @message is configured
|
||||
# for the test, since MessageItem assumes attributes of the message will not
|
||||
# change after getInitialState runs.
|
||||
|
@ -110,16 +112,31 @@ describe "MessageItem", ->
|
|||
collapsed={collapsed}
|
||||
thread_participants={@threadParticipants} />
|
||||
)
|
||||
|
||||
describe "when collapsed", ->
|
||||
|
||||
# TODO: We currently don't support collapsed messages
|
||||
# describe "when collapsed", ->
|
||||
# beforeEach ->
|
||||
# @createComponent({collapsed: true})
|
||||
#
|
||||
# it "should not render the EmailFrame", ->
|
||||
# expect( -> ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub)).toThrow()
|
||||
#
|
||||
# it "should have the `collapsed` class", ->
|
||||
# expect(@component.getDOMNode().className.indexOf('collapsed') >= 0).toBe(true)
|
||||
|
||||
describe "when displaying detailed headers", ->
|
||||
beforeEach ->
|
||||
@createComponent({collapsed: true})
|
||||
@createComponent({collapsed: false})
|
||||
@component.setState detailedHeaders: true
|
||||
|
||||
it "should not render the EmailFrame", ->
|
||||
expect( -> ReactTestUtils.findRenderedComponentWithType(@component, EmailFrameStub)).toThrow()
|
||||
it "correctly sets the participant states", ->
|
||||
participants = ReactTestUtils.findRenderedDOMComponentWithClass(@component, "expanded-participants")
|
||||
expect(participants).toBeDefined()
|
||||
expect(-> ReactTestUtils.findRenderedDOMComponentWithClass(@component, "collapsed-participants")).toThrow()
|
||||
|
||||
it "should have the `collapsed` class", ->
|
||||
expect(@component.getDOMNode().className.indexOf('collapsed') >= 0).toBe(true)
|
||||
it "correctly sets the timestamp", ->
|
||||
ts = ReactTestUtils.findRenderedComponentWithType(@component, MessageTimestamp)
|
||||
expect(ts.props.isDetailed).toBe true
|
||||
|
||||
describe "when not collapsed", ->
|
||||
beforeEach ->
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
_ = require 'underscore-plus'
|
||||
React = require "react/addons"
|
||||
ReactTestUtils = React.addons.TestUtils
|
||||
TestUtils = React.addons.TestUtils
|
||||
{Contact, Message} = require "inbox-exports"
|
||||
MessageParticipants = require "../lib/message-participants.cjsx"
|
||||
|
||||
user_1 =
|
||||
name: "User One"
|
||||
email: "user1@inboxapp.com"
|
||||
email: "user1@nilas.com"
|
||||
user_2 =
|
||||
name: "User Two"
|
||||
email: "user2@inboxapp.com"
|
||||
email: "user2@nilas.com"
|
||||
user_3 =
|
||||
name: "User Three"
|
||||
email: "user3@inboxapp.com"
|
||||
email: "user3@nilas.com"
|
||||
user_4 =
|
||||
name: "User Four"
|
||||
email: "user4@inboxapp.com"
|
||||
email: "user4@nilas.com"
|
||||
user_5 =
|
||||
name: "User Five"
|
||||
email: "user5@inboxapp.com"
|
||||
email: "user5@nilas.com"
|
||||
|
||||
many_users = (new Contact({name: "User #{i}", email:"#{i}@app.com"}) for i in [0..100])
|
||||
|
||||
|
@ -54,34 +55,74 @@ thread2_participants = [
|
|||
]
|
||||
|
||||
describe "MessageParticipants", ->
|
||||
it "determines the message is to everyone", ->
|
||||
p1 = TestUtils.renderIntoDocument(
|
||||
<MessageParticipants to={big_test_message.to}
|
||||
cc={big_test_message.cc}
|
||||
from={big_test_message.from}
|
||||
thread_participants={many_thread_users}
|
||||
message_participants={big_test_message.participants()} />
|
||||
)
|
||||
expect(p1._isToEveryone()).toBe true
|
||||
describe "when collapsed", ->
|
||||
beforeEach ->
|
||||
@participants = TestUtils.renderIntoDocument(
|
||||
<MessageParticipants to={test_message.to}
|
||||
cc={test_message.cc}
|
||||
from={test_message.from}
|
||||
thread_participants={many_thread_users}
|
||||
message_participants={test_message.participants()} />
|
||||
)
|
||||
|
||||
it "knows when the message isn't to everyone due to participant mismatch", ->
|
||||
p2 = TestUtils.renderIntoDocument(
|
||||
<MessageParticipants to={test_message.to}
|
||||
cc={test_message.cc}
|
||||
from={test_message.from}
|
||||
thread_participants={thread2_participants}
|
||||
message_participants={test_message.participants()} />
|
||||
)
|
||||
# this should be false because we don't count bccs
|
||||
expect(p2._isToEveryone()).toBe false
|
||||
it "renders into the document", ->
|
||||
participants = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "collapsed-participants")
|
||||
expect(participants).toBeDefined()
|
||||
|
||||
it "knows when the message isn't to everyone due to participant size", ->
|
||||
p2 = TestUtils.renderIntoDocument(
|
||||
<MessageParticipants to={test_message.to}
|
||||
cc={test_message.cc}
|
||||
from={test_message.from}
|
||||
thread_participants={thread_participants}
|
||||
message_participants={test_message.participants()} />
|
||||
)
|
||||
# this should be false because we don't count bccs
|
||||
expect(p2._isToEveryone()).toBe false
|
||||
it "uses short names", ->
|
||||
to = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "to-contact")
|
||||
expect(to.getDOMNode().innerHTML).toBe "User"
|
||||
|
||||
describe "when expanded", ->
|
||||
beforeEach ->
|
||||
@participants = TestUtils.renderIntoDocument(
|
||||
<MessageParticipants to={test_message.to}
|
||||
cc={test_message.cc}
|
||||
from={test_message.from}
|
||||
thread_participants={many_thread_users}
|
||||
detailedParticipants={true}
|
||||
message_participants={test_message.participants()} />
|
||||
)
|
||||
|
||||
it "renders into the document", ->
|
||||
participants = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "expanded-participants")
|
||||
expect(participants).toBeDefined()
|
||||
|
||||
it "uses full names", ->
|
||||
to = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "to-contact")
|
||||
expect(to.getDOMNode().innerHTML).toBe "User Two <user2@nilas.com>"
|
||||
|
||||
|
||||
# TODO: We no longer display "to everyone"
|
||||
#
|
||||
# it "determines the message is to everyone", ->
|
||||
# p1 = TestUtils.renderIntoDocument(
|
||||
# <MessageParticipants to={big_test_message.to}
|
||||
# cc={big_test_message.cc}
|
||||
# from={big_test_message.from}
|
||||
# thread_participants={many_thread_users}
|
||||
# message_participants={big_test_message.participants()} />
|
||||
# )
|
||||
# expect(p1._isToEveryone()).toBe true
|
||||
#
|
||||
# it "knows when the message isn't to everyone due to participant mismatch", ->
|
||||
# p2 = TestUtils.renderIntoDocument(
|
||||
# <MessageParticipants to={test_message.to}
|
||||
# cc={test_message.cc}
|
||||
# from={test_message.from}
|
||||
# thread_participants={thread2_participants}
|
||||
# message_participants={test_message.participants()} />
|
||||
# )
|
||||
# # this should be false because we don't count bccs
|
||||
# expect(p2._isToEveryone()).toBe false
|
||||
#
|
||||
# it "knows when the message isn't to everyone due to participant size", ->
|
||||
# p2 = TestUtils.renderIntoDocument(
|
||||
# <MessageParticipants to={test_message.to}
|
||||
# cc={test_message.cc}
|
||||
# from={test_message.from}
|
||||
# thread_participants={thread_participants}
|
||||
# message_participants={test_message.participants()} />
|
||||
# )
|
||||
# # this should be false because we don't count bccs
|
||||
# expect(p2._isToEveryone()).toBe false
|
||||
|
|
|
@ -11,8 +11,7 @@ describe "MessageTimestamp", ->
|
|||
@item = TestUtils.renderIntoDocument(
|
||||
<MessageTimestamp date={testDate()} />
|
||||
)
|
||||
@itemNode = @item.getDOMNode()
|
||||
|
||||
|
||||
# test messsage time is 1415814587
|
||||
it "displays the time from messages LONG ago", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(2, 'years')
|
||||
|
@ -33,3 +32,10 @@ describe "MessageTimestamp", ->
|
|||
it "displays the time from messages recently", ->
|
||||
spyOn(@item, "_today").andCallFake -> testDate().add(2, 'hours')
|
||||
expect(@item._timeFormat()).toBe "h:mm a"
|
||||
|
||||
it "displays the full time when in detailed timestamp mode", ->
|
||||
itemDetailed = TestUtils.renderIntoDocument(
|
||||
<MessageTimestamp date={testDate()} detailedTimestamp={true} />
|
||||
)
|
||||
spyOn(itemDetailed, "_today").andCallFake -> testDate()
|
||||
expect(itemDetailed._timeFormat()).toBe "ddd, MMM Do YYYY, h:mm:ss a z"
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
padding: @spacing-standard @spacing-double;
|
||||
|
||||
.message-header {
|
||||
position: relative;
|
||||
font-size: @font-size-small;
|
||||
.message-actions {
|
||||
float:right;
|
||||
|
@ -88,41 +89,18 @@
|
|||
|
||||
.message-time, .message-indicator {
|
||||
color: @text-color-very-subtle;
|
||||
margin-top: 3px;
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.message-indicator {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.to-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.to-label, .to-everyone, .cc-label, .cc-contact, .to-label, .to-contact {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.from-label {
|
||||
display: none;
|
||||
}
|
||||
.from-contact {
|
||||
font-weight: @headings-font-weight;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.to-label, .cc-label {
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
.cc-label {
|
||||
margin-left: @spacing-standard;
|
||||
}
|
||||
.to-contact, .cc-contact, .to-everyone {
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
|
@ -131,7 +109,64 @@
|
|||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.collapse-headers {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
|
||||
.collapse-headers:hover {cursor: pointer;}
|
||||
|
||||
}
|
||||
.attachments-area {
|
||||
padding-top: @spacing-standard;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
// message-participants.cjsx //
|
||||
///////////////////////////////
|
||||
.message-participants {
|
||||
|
||||
&.collapsed:hover {cursor: pointer;}
|
||||
|
||||
.from-contact {
|
||||
font-weight: @headings-font-weight;
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
.to-label, .cc-label {
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
.cc-label {
|
||||
margin-left: @spacing-standard;
|
||||
}
|
||||
.to-contact, .cc-contact, .to-everyone {
|
||||
color: @text-color-very-subtle;
|
||||
}
|
||||
|
||||
.to-label { font-weight: 600; }
|
||||
|
||||
.expanded-participants {
|
||||
position: relative;
|
||||
padding-right: 1.2em;
|
||||
|
||||
.from-label, .to-label, .cc-label {
|
||||
float: left;
|
||||
display: block;
|
||||
font-weight: 600;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.to-label, .cc-label {
|
||||
margin-right: 1.15em;
|
||||
}
|
||||
|
||||
.from-contact, .to-contact, .cc-contact {
|
||||
padding-left: 2.85em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"marked": "^0.3",
|
||||
"mkdirp": "^0.5",
|
||||
"moment": "^2.8",
|
||||
"moment-timezone": "^0.3",
|
||||
"nslog": "^2.0.0",
|
||||
"oniguruma": "^4.0.0",
|
||||
"optimist": "0.4.0",
|
||||
|
|
|
@ -193,4 +193,4 @@ describe "DraftStore", ->
|
|||
@_callNewMessageWithContext {threadId: fakeThread.id, messageId: fakeMessage1.id}
|
||||
, (thread, message) ->
|
||||
expect(message).toEqual(fakeMessage1)
|
||||
{}
|
||||
{}
|
||||
|
|
|
@ -21,6 +21,12 @@ class Contact extends Model
|
|||
json['name'] ||= json['email']
|
||||
json
|
||||
|
||||
displayFullContact: ->
|
||||
if @name and @name isnt @email
|
||||
return "#{@name} <#{@email}>"
|
||||
else
|
||||
return @email
|
||||
|
||||
displayName: ->
|
||||
return "You" if @email == NamespaceStore.current().emailAddress
|
||||
@_nameParts().join(' ')
|
||||
|
|
|
@ -88,7 +88,6 @@ Utils =
|
|||
|
||||
Utils.images = {}
|
||||
Utils.images[path.basename(file)] = file for file in files
|
||||
# console.log("Loaded image map in #{Date.now()-start}msec")
|
||||
|
||||
if window.devicePixelRatio > 1
|
||||
return Utils.images["#{name}@2x.#{ext}"] ? Utils.images[fullname] ? Utils.images["#{name}@1x.#{ext}"]
|
||||
|
|
Loading…
Add table
Reference in a new issue