feat(message): change long format timestamp
Summary: Minor UI fixes Test Plan: edgehill --test Reviewers: bengotow Reviewed By: bengotow Differential Revision: https://review.inboxapp.com/D1271
|
@ -4,8 +4,9 @@ module.exports =
|
||||||
# Models
|
# Models
|
||||||
Menu: require '../src/components/menu'
|
Menu: require '../src/components/menu'
|
||||||
Popover: require '../src/components/popover'
|
Popover: require '../src/components/popover'
|
||||||
TokenizingTextField: require '../src/components/tokenizing-text-field'
|
Tooltip: require '../src/components/tooltip'
|
||||||
ResizableRegion: require '../src/components/resizable-region'
|
|
||||||
Flexbox: require '../src/components/flexbox'
|
Flexbox: require '../src/components/flexbox'
|
||||||
RetinaImg: require '../src/components/retina-img'
|
RetinaImg: require '../src/components/retina-img'
|
||||||
ListTabular: require '../src/components/list-tabular'
|
ListTabular: require '../src/components/list-tabular'
|
||||||
|
ResizableRegion: require '../src/components/resizable-region'
|
||||||
|
TokenizingTextField: require '../src/components/tokenizing-text-field'
|
||||||
|
|
|
@ -3,13 +3,16 @@ _ = require 'underscore-plus'
|
||||||
EmailFrame = require './email-frame'
|
EmailFrame = require './email-frame'
|
||||||
MessageParticipants = require "./message-participants.cjsx"
|
MessageParticipants = require "./message-participants.cjsx"
|
||||||
MessageTimestamp = require "./message-timestamp.cjsx"
|
MessageTimestamp = require "./message-timestamp.cjsx"
|
||||||
{ComponentRegistry, FileDownloadStore, Utils} = require 'inbox-exports'
|
{ComponentRegistry, FileDownloadStore, Utils, Actions} = require 'inbox-exports'
|
||||||
|
{RetinaImg} = require 'ui-components'
|
||||||
Autolinker = require 'autolinker'
|
Autolinker = require 'autolinker'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
MessageItem = React.createClass
|
MessageItem = React.createClass
|
||||||
displayName: 'MessageItem'
|
displayName: 'MessageItem'
|
||||||
|
|
||||||
propTypes:
|
propTypes:
|
||||||
|
thread: React.PropTypes.object.isRequired
|
||||||
message: React.PropTypes.object.isRequired
|
message: React.PropTypes.object.isRequired
|
||||||
thread_participants: React.PropTypes.arrayOf(React.PropTypes.object)
|
thread_participants: React.PropTypes.arrayOf(React.PropTypes.object)
|
||||||
collapsed: React.PropTypes.bool
|
collapsed: React.PropTypes.bool
|
||||||
|
@ -31,7 +34,6 @@ MessageItem = React.createClass
|
||||||
@_storeUnlisten() if @_storeUnlisten
|
@_storeUnlisten() if @_storeUnlisten
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
messageActions = ComponentRegistry.findAllViewsByRole('MessageAction')
|
|
||||||
messageIndicators = ComponentRegistry.findAllViewsByRole('MessageIndicator')
|
messageIndicators = ComponentRegistry.findAllViewsByRole('MessageIndicator')
|
||||||
attachments = @_attachmentComponents()
|
attachments = @_attachmentComponents()
|
||||||
if attachments.length > 0
|
if attachments.length > 0
|
||||||
|
@ -40,30 +42,29 @@ MessageItem = React.createClass
|
||||||
header =
|
header =
|
||||||
<header className="message-header">
|
<header className="message-header">
|
||||||
|
|
||||||
<MessageTimestamp className="message-time"
|
<div className="message-header-right">
|
||||||
onClick={=> @setState detailedHeaders: true}
|
<MessageTimestamp className="message-time"
|
||||||
isDetailed={@state.detailedHeaders}
|
isDetailed={@state.detailedHeaders}
|
||||||
date={@props.message.date} />
|
date={@props.message.date} />
|
||||||
|
|
||||||
<div className="message-actions">
|
{<div className="message-indicator"><Indicator message={@props.message}/></div> for Indicator in messageIndicators}
|
||||||
{<Action thread={@props.thread} message={@props.message} /> for Action in messageActions}
|
|
||||||
|
{if @state.detailedHeaders then @_renderMessageActionsInline() else @_renderMessageActionsTooltip()}
|
||||||
</div>
|
</div>
|
||||||
{<div className="message-indicator"><Indicator message={@props.message}/></div> for Indicator in messageIndicators}
|
|
||||||
<MessageParticipants to={@props.message.to}
|
<MessageParticipants to={@props.message.to}
|
||||||
cc={@props.message.cc}
|
cc={@props.message.cc}
|
||||||
from={@props.message.from}
|
from={@props.message.from}
|
||||||
onClick={=> @setState detailedHeaders: true}
|
onClick={=> @setState detailedHeaders: true}
|
||||||
thread_participants={@props.thread_participants}
|
thread_participants={@props.thread_participants}
|
||||||
detailedParticipants={@state.detailedHeaders}
|
isDetailed={@state.detailedHeaders}
|
||||||
message_participants={@props.message.participants()} />
|
message_participants={@props.message.participants()} />
|
||||||
|
|
||||||
<div className="collapse-headers"
|
{@_renderCollapseControl()}
|
||||||
style={if @state.detailedHeaders then {display: "block"} else {display: "none"}}
|
|
||||||
onClick={=> @setState detailedHeaders: false}><i className="fa fa-chevron-up"></i>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="message-item-wrap">
|
<div className={@props.className}>
|
||||||
<div className="message-item-area">
|
<div className="message-item-area">
|
||||||
{header}
|
{header}
|
||||||
{attachments}
|
{attachments}
|
||||||
|
@ -79,6 +80,58 @@ MessageItem = React.createClass
|
||||||
'no-quoted-text': !Utils.containsQuotedText(@props.message.body)
|
'no-quoted-text': !Utils.containsQuotedText(@props.message.body)
|
||||||
'show-quoted-text': @state.showQuotedText
|
'show-quoted-text': @state.showQuotedText
|
||||||
|
|
||||||
|
_renderMessageActionsInline: ->
|
||||||
|
@_renderMessageActions()
|
||||||
|
|
||||||
|
_renderMessageActionsTooltip: ->
|
||||||
|
## TODO: Use Tooltip UI Component
|
||||||
|
<span className="msg-actions-tooltip"
|
||||||
|
onClick={=> @setState detailedHeaders: true}>
|
||||||
|
<RetinaImg name={"message-show-more.png"}/></span>
|
||||||
|
|
||||||
|
_renderMessageActions: ->
|
||||||
|
messageActions = ComponentRegistry.findAllViewsByRole('MessageAction')
|
||||||
|
<div className="message-actions">
|
||||||
|
<button className="btn btn-icon" onClick={@_onReply}>
|
||||||
|
<RetinaImg name={"message-reply.png"}/>
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-icon" onClick={@_onReplyAll}>
|
||||||
|
<RetinaImg name={"message-reply-all.png"}/>
|
||||||
|
</button>
|
||||||
|
<button className="btn btn-icon" onClick={@_onForward}>
|
||||||
|
<RetinaImg name={"message-forward.png"}/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{<Action thread={@props.thread} message={@props.message} /> for Action in messageActions}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_onReply: ->
|
||||||
|
tId = @props.thread.id; mId = @props.message.id
|
||||||
|
Actions.composeReply(threadId: tId, messageId: mId) if (tId and mId)
|
||||||
|
|
||||||
|
_onReplyAll: ->
|
||||||
|
tId = @props.thread.id; mId = @props.message.id
|
||||||
|
Actions.composeReplyAll(threadId: tId, messageId: mId) if (tId and mId)
|
||||||
|
|
||||||
|
_onForward: ->
|
||||||
|
tId = @props.thread.id; mId = @props.message.id
|
||||||
|
Actions.composeForward(threadId: tId, messageId: mId) if (tId and mId)
|
||||||
|
|
||||||
|
_renderCollapseControl: ->
|
||||||
|
if @state.detailedHeaders
|
||||||
|
<div className="collapse-control"
|
||||||
|
style={top: "-1px", left: "-17px"}
|
||||||
|
onClick={=> @setState detailedHeaders: false}>
|
||||||
|
<RetinaImg name={"message-disclosure-triangle-active.png"}/>
|
||||||
|
</div>
|
||||||
|
else
|
||||||
|
<div className="collapse-control inactive"
|
||||||
|
style={top: "-2px"}
|
||||||
|
onClick={=> @setState detailedHeaders: true}>
|
||||||
|
<RetinaImg name={"message-disclosure-triangle.png"}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
# Eventually, _formatBody will run a series of registered body transformers.
|
# Eventually, _formatBody will run a series of registered body transformers.
|
||||||
# For now, it just runs a few we've hardcoded here, which are all synchronous.
|
# For now, it just runs a few we've hardcoded here, which are all synchronous.
|
||||||
_formatBody: ->
|
_formatBody: ->
|
||||||
|
|
|
@ -16,6 +16,8 @@ MessageList = React.createClass
|
||||||
@_unsubscribers = []
|
@_unsubscribers = []
|
||||||
@_unsubscribers.push MessageStore.listen @_onChange
|
@_unsubscribers.push MessageStore.listen @_onChange
|
||||||
@_unsubscribers.push ThreadStore.listen @_onChange
|
@_unsubscribers.push ThreadStore.listen @_onChange
|
||||||
|
@_lastHeight = -1
|
||||||
|
@_scrollToBottom()
|
||||||
|
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
unsubscribe() for unsubscribe in @_unsubscribers
|
unsubscribe() for unsubscribe in @_unsubscribers
|
||||||
|
@ -26,6 +28,8 @@ MessageList = React.createClass
|
||||||
@_focusComposerId = newDrafts[0]
|
@_focusComposerId = newDrafts[0]
|
||||||
|
|
||||||
componentDidUpdate: ->
|
componentDidUpdate: ->
|
||||||
|
@_lastHeight = -1
|
||||||
|
@_scrollToBottom()
|
||||||
if @_focusComposerId?
|
if @_focusComposerId?
|
||||||
@_focusRef(@refs["composerItem-#{@_focusComposerId}"])
|
@_focusRef(@refs["composerItem-#{@_focusComposerId}"])
|
||||||
@_focusComposerId = null
|
@_focusComposerId = null
|
||||||
|
@ -41,7 +45,7 @@ MessageList = React.createClass
|
||||||
return <div></div> if not @state.current_thread?
|
return <div></div> if not @state.current_thread?
|
||||||
|
|
||||||
<div className="message-list" id="message-list">
|
<div className="message-list" id="message-list">
|
||||||
<div tabIndex=1 className="messages-wrap">
|
<div tabIndex=1 ref="messageWrap" className="messages-wrap">
|
||||||
<div className="message-list-notification-bars">
|
<div className="message-list-notification-bars">
|
||||||
{@_messageListNotificationBars()}
|
{@_messageListNotificationBars()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,7 +88,7 @@ MessageList = React.createClass
|
||||||
|
|
||||||
_messageComponents: ->
|
_messageComponents: ->
|
||||||
ComposerItem = @state.Composer
|
ComposerItem = @state.Composer
|
||||||
containsUnread = _.any @state.messages, (m) -> m.unread
|
# containsUnread = _.any @state.messages, (m) -> m.unread
|
||||||
collapsed = false
|
collapsed = false
|
||||||
components = []
|
components = []
|
||||||
|
|
||||||
|
@ -94,12 +98,15 @@ MessageList = React.createClass
|
||||||
ref="composerItem-#{message.id}"
|
ref="composerItem-#{message.id}"
|
||||||
key={@state.messageLocalIds[message.id]}
|
key={@state.messageLocalIds[message.id]}
|
||||||
localId={@state.messageLocalIds[message.id]}
|
localId={@state.messageLocalIds[message.id]}
|
||||||
containerClass="message-item-wrap"/>
|
containerClass="message-item-wrap draft-message"/>
|
||||||
else
|
else
|
||||||
|
className = "message-item-wrap"
|
||||||
|
if message.unread then className += " unread-message"
|
||||||
components.push <MessageItem key={message.id}
|
components.push <MessageItem key={message.id}
|
||||||
thread={@state.current_thread}
|
thread={@state.current_thread}
|
||||||
message={message}
|
message={message}
|
||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
|
className={className}
|
||||||
thread_participants={@_threadParticipants()} />
|
thread_participants={@_threadParticipants()} />
|
||||||
|
|
||||||
components
|
components
|
||||||
|
@ -125,6 +132,35 @@ MessageList = React.createClass
|
||||||
participants[contact.email] = contact
|
participants[contact.email] = contact
|
||||||
return _.values(participants)
|
return _.values(participants)
|
||||||
|
|
||||||
|
# There may be a lot of iframes to load which may take an indeterminate
|
||||||
|
# amount of time. As long as there is more content being painted onto
|
||||||
|
# the page, we keep trying to scroll to the bottom. We scroll to the top
|
||||||
|
# of the last message.
|
||||||
|
#
|
||||||
|
# We don't scroll if there's only 1 item.
|
||||||
|
# We don't screll if you're actively focused somewhere in the message
|
||||||
|
# list.
|
||||||
|
_scrollToBottom: ->
|
||||||
|
_.defer =>
|
||||||
|
if @isMounted()
|
||||||
|
messageWrap = @refs?.messageWrap?.getDOMNode?()
|
||||||
|
|
||||||
|
return if not messageWrap?
|
||||||
|
return if messageWrap.children <= 1
|
||||||
|
return if @getDOMNode().contains document.activeElement
|
||||||
|
|
||||||
|
msgToScroll = messageWrap.querySelector(".draft-message, .unread-message")
|
||||||
|
if not msgToScroll?
|
||||||
|
msgToScroll = messageWrap.children[messageWrap.children.length - 1]
|
||||||
|
|
||||||
|
currentHeight = messageWrap.getBoundingClientRect().height
|
||||||
|
|
||||||
|
if currentHeight isnt @_lastHeight
|
||||||
|
@_lastHeight = currentHeight
|
||||||
|
@_scrollToBottom()
|
||||||
|
else
|
||||||
|
scrollTo = currentHeight - msgToScroll.getBoundingClientRect().height
|
||||||
|
@getDOMNode().scrollTop = scrollTo
|
||||||
|
|
||||||
MessageList.minWidth = 600
|
MessageList.minWidth = 600
|
||||||
MessageList.maxWidth = 900
|
MessageList.maxWidth = 900
|
||||||
|
|
|
@ -9,10 +9,10 @@ MessageParticipants = React.createClass
|
||||||
classSet = React.addons.classSet
|
classSet = React.addons.classSet
|
||||||
"participants": true
|
"participants": true
|
||||||
"message-participants": true
|
"message-participants": true
|
||||||
"collapsed": not @props.detailedParticipants
|
"collapsed": not @props.isDetailed
|
||||||
|
|
||||||
<div className={classSet} onClick={@props.onClick}>
|
<div className={classSet} onClick={@props.onClick}>
|
||||||
{if @props.detailedParticipants then @_renderExpanded() else @_renderCollapsed()}
|
{if @props.isDetailed then @_renderExpanded() else @_renderCollapsed()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_renderCollapsed: ->
|
_renderCollapsed: ->
|
||||||
|
@ -28,17 +28,18 @@ MessageParticipants = React.createClass
|
||||||
|
|
||||||
_renderExpanded: ->
|
_renderExpanded: ->
|
||||||
<div className="expanded-participants">
|
<div className="expanded-participants">
|
||||||
<div>
|
<div className="participant-type">
|
||||||
<div className="participant-label from-label">From: </div>
|
<div className="participant-label from-label">From: </div>
|
||||||
<div className="participant-name from-contact">{@_fullContact(@props.from)}</div>
|
<div className="participant-name from-contact">{@_fullContact(@props.from)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="participant-type">
|
||||||
<div className="participant-label to-label">To: </div>
|
<div className="participant-label to-label">To: </div>
|
||||||
<div className="participant-name to-contact">{@_fullContact(@props.to)}</div>
|
<div className="participant-name to-contact">{@_fullContact(@props.to)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={if @props.cc.length > 0 then display:"inline" else display:"none"}>
|
<div className="participant-type"
|
||||||
|
style={if @props.cc.length > 0 then display:"block" else display:"none"}>
|
||||||
<div className="participant-label cc-label">Cc: </div>
|
<div className="participant-label cc-label">Cc: </div>
|
||||||
<div className="participant-name cc-contact">{@_fullContact(@props.cc)}</div>
|
<div className="participant-name cc-contact">{@_fullContact(@props.cc)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,4 +49,14 @@ MessageParticipants = React.createClass
|
||||||
_.map(contacts, (c) -> c.displayFirstName()).join(", ")
|
_.map(contacts, (c) -> c.displayFirstName()).join(", ")
|
||||||
|
|
||||||
_fullContact: (contacts=[]) ->
|
_fullContact: (contacts=[]) ->
|
||||||
_.map(contacts, (c) -> c.displayFullContact()).join(", ")
|
_.map(contacts, (c) ->
|
||||||
|
if c.name?.length > 0 and c.name isnt c.email
|
||||||
|
<div key={c.email} className="participant">
|
||||||
|
<span className="participant-primary">{c.name}</span>
|
||||||
|
<span className="participant-secondary"><{c.email}></span>
|
||||||
|
</div>
|
||||||
|
else
|
||||||
|
<div key={c.email} className="participant">
|
||||||
|
<span className="participant-primary">{c.email}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
|
@ -19,7 +19,7 @@ MessageTimestamp = React.createClass
|
||||||
|
|
||||||
_timeFormat: ->
|
_timeFormat: ->
|
||||||
if @props.isDetailed
|
if @props.isDetailed
|
||||||
return "ddd, MMM Do YYYY, h:mm:ss a z"
|
return "DD / MM / YYYY h:mm a z"
|
||||||
else
|
else
|
||||||
today = moment(@_today())
|
today = moment(@_today())
|
||||||
dayOfEra = today.dayOfYear() + today.year() * 365
|
dayOfEra = today.dayOfYear() + today.year() * 365
|
||||||
|
|
|
@ -80,7 +80,7 @@ describe "MessageParticipants", ->
|
||||||
cc={test_message.cc}
|
cc={test_message.cc}
|
||||||
from={test_message.from}
|
from={test_message.from}
|
||||||
thread_participants={many_thread_users}
|
thread_participants={many_thread_users}
|
||||||
detailedParticipants={true}
|
isDetailed={true}
|
||||||
message_participants={test_message.participants()} />
|
message_participants={test_message.participants()} />
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ describe "MessageParticipants", ->
|
||||||
|
|
||||||
it "uses full names", ->
|
it "uses full names", ->
|
||||||
to = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "to-contact")
|
to = ReactTestUtils.findRenderedDOMComponentWithClass(@participants, "to-contact")
|
||||||
expect(to.getDOMNode().innerHTML).toBe "User Two <user2@nilas.com>"
|
expect(to.getDOMNode().innerText).toEqual "User Two <user2@nilas.com>"
|
||||||
|
|
||||||
|
|
||||||
# TODO: We no longer display "to everyone"
|
# TODO: We no longer display "to everyone"
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe "MessageTimestamp", ->
|
||||||
|
|
||||||
it "displays the full time when in detailed timestamp mode", ->
|
it "displays the full time when in detailed timestamp mode", ->
|
||||||
itemDetailed = TestUtils.renderIntoDocument(
|
itemDetailed = TestUtils.renderIntoDocument(
|
||||||
<MessageTimestamp date={testDate()} detailedTimestamp={true} />
|
<MessageTimestamp date={testDate()} isDetailed={true} />
|
||||||
)
|
)
|
||||||
spyOn(itemDetailed, "_today").andCallFake -> testDate()
|
spyOn(itemDetailed, "_today").andCallFake -> testDate()
|
||||||
expect(itemDetailed._timeFormat()).toBe "ddd, MMM Do YYYY, h:mm:ss a z"
|
expect(itemDetailed._timeFormat()).toBe "DD / MM / YYYY h:mm a z"
|
||||||
|
|
|
@ -65,42 +65,53 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-header {
|
||||||
|
position: relative;
|
||||||
|
font-size: @font-size-small;
|
||||||
|
|
||||||
|
.message-actions {
|
||||||
|
z-index: 4;
|
||||||
|
margin-top: 0.35em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
padding: 0 @spacing-half;
|
||||||
|
&:last-child { padding-right: 0; }
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 0;
|
||||||
|
&:active {background: transparent;}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
z-index: 2; position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.msg-actions-tooltip {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
.message-participants { z-index: 1; position: relative; }
|
||||||
|
|
||||||
|
.message-time, .message-indicator {
|
||||||
|
color: @text-color-very-subtle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.message-header-right {
|
||||||
|
z-index: 4; position: relative;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.message-item-area {
|
.message-item-area {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @message-max-width;
|
max-width: @message-max-width;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: @spacing-standard @spacing-double;
|
padding: @spacing-standard @spacing-double;
|
||||||
|
|
||||||
.message-header {
|
|
||||||
position: relative;
|
|
||||||
font-size: @font-size-small;
|
|
||||||
.message-actions {
|
|
||||||
float:right;
|
|
||||||
padding-right:15px;
|
|
||||||
.btn-icon {
|
|
||||||
font-size:16px;
|
|
||||||
color:rgba(35, 31, 32, 0.1);
|
|
||||||
&:hover {
|
|
||||||
color:rgba(35, 31, 32, 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-time, .message-indicator {
|
|
||||||
color: @text-color-very-subtle;
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-indicator {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
@ -111,14 +122,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.collapse-headers {
|
.collapse-control {
|
||||||
|
&.inactive { display: none; }
|
||||||
|
z-index: 3;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
top: 0;
|
||||||
right: 0;
|
left: -1 * @spacing-standard;
|
||||||
color: @text-color-very-subtle;
|
color: @text-color-very-subtle;
|
||||||
}
|
}
|
||||||
|
.message-item-wrap:hover {
|
||||||
|
.collapse-control.inactive { display: block; }
|
||||||
|
}
|
||||||
|
|
||||||
.collapse-headers:hover {cursor: pointer;}
|
.collapse-control:hover {cursor: pointer;}
|
||||||
|
|
||||||
}
|
}
|
||||||
.attachments-area {
|
.attachments-area {
|
||||||
|
@ -137,36 +153,44 @@
|
||||||
font-weight: @headings-font-weight;
|
font-weight: @headings-font-weight;
|
||||||
color: @text-color;
|
color: @text-color;
|
||||||
}
|
}
|
||||||
|
.from-label, .to-label, .cc-label {
|
||||||
.to-label, .cc-label {
|
|
||||||
color: @text-color-very-subtle;
|
color: @text-color-very-subtle;
|
||||||
}
|
}
|
||||||
.cc-label {
|
.to-label { font-weight: 600; }
|
||||||
margin-left: @spacing-standard;
|
.cc-label { margin-left: @spacing-standard; }
|
||||||
}
|
|
||||||
.to-contact, .cc-contact, .to-everyone {
|
.to-contact, .cc-contact, .to-everyone {
|
||||||
color: @text-color-very-subtle;
|
color: @text-color-very-subtle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.to-label { font-weight: 600; }
|
|
||||||
|
|
||||||
.expanded-participants {
|
.expanded-participants {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 1.2em;
|
padding-right: 1.2em;
|
||||||
|
|
||||||
|
.participant-type {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
&:first-child {margin-top: 0;}
|
||||||
|
}
|
||||||
|
|
||||||
.from-label, .to-label, .cc-label {
|
.from-label, .to-label, .cc-label {
|
||||||
float: left;
|
float: left;
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.to-label, .cc-label {
|
.from-contact, .to-contact, .cc-contact {
|
||||||
margin-right: 1.15em;
|
padding-left: 3.85em;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.from-contact, .to-contact, .cc-contact {
|
.from-label { margin-right: 1em; }
|
||||||
padding-left: 2.85em;
|
.to-label, .cc-label { margin-right: 2.15em; }
|
||||||
|
|
||||||
|
.participant-primary {
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
.participant-secondary {
|
||||||
|
color: @text-color-very-subtle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/components/tooltip.cjsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
_ = require 'underscore-plus'
|
||||||
|
React = require 'react/addons'
|
||||||
|
|
||||||
|
###
|
||||||
|
The Tooltip component displays a consistent hovering tooltip for use when
|
||||||
|
extra context information is required.
|
||||||
|
###
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
Tooltip = React.createClass
|
||||||
|
render: ->
|
||||||
|
<div className="tooltip" style={@_styles()}>{@props.children}</div>
|
||||||
|
|
||||||
|
_styles: ->
|
||||||
|
@props.modifierElement
|
After Width: | Height: | Size: 345 B |
BIN
static/images/message-list/message-disclosure-triangle@2x.png
Normal file
After Width: | Height: | Size: 349 B |
BIN
static/images/message-list/message-forward@1x.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
static/images/message-list/message-forward@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/message-list/message-reply-all@1x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/message-list/message-reply-all@2x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/message-list/message-reply@1x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
static/images/message-list/message-reply@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
static/images/message-list/message-show-more@1x.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
static/images/message-list/message-show-more@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
|
@ -196,7 +196,9 @@
|
||||||
//** Global color for active items (e.g., navs or dropdowns).
|
//** Global color for active items (e.g., navs or dropdowns).
|
||||||
@component-active-color: #fff;
|
@component-active-color: #fff;
|
||||||
//** Global background color for active items (e.g., navs or dropdowns).
|
//** Global background color for active items (e.g., navs or dropdowns).
|
||||||
@component-active-bg: #3a3e44;
|
// @component-active-bg: #3a3e44;
|
||||||
|
// @component-active-bg: #116cd6;
|
||||||
|
@component-active-bg: @nilas-blue;
|
||||||
|
|
||||||
|
|
||||||
//============================== Tables ===============================//
|
//============================== Tables ===============================//
|
||||||
|
|