Merge branch 'master' of github.com:nylas/N1
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"rules": {
|
||||
"react/prop-types": [2, {"ignore": ["children"]}],
|
||||
"react/no-multi-comp": [1],
|
||||
"react/no-multi-comp": [0],
|
||||
"eqeqeq": [2, "smart"],
|
||||
"id-length": [0],
|
||||
"object-curly-spacing": [0],
|
||||
|
|
After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
@ -25,8 +25,6 @@ class TemplatePicker extends React.Component {
|
|||
if (this.unsubscribe) this.unsubscribe();
|
||||
}
|
||||
|
||||
static containerStyles = {order: 2};
|
||||
|
||||
_filteredTemplates(search = this.state.searchValue) {
|
||||
const items = TemplateStore.items();
|
||||
|
||||
|
@ -66,7 +64,7 @@ class TemplatePicker extends React.Component {
|
|||
|
||||
render() {
|
||||
const button = (
|
||||
<button className="btn btn-toolbar narrow">
|
||||
<button className="btn btn-toolbar narrow" title="Insert email template">
|
||||
<RetinaImg url="nylas://composer-templates/assets/icon-composer-templates@2x.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
|
||||
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 48 KiB |
|
@ -71,8 +71,9 @@ class TranslateButton extends React.Component
|
|||
#
|
||||
_renderButton: =>
|
||||
<button className="btn btn-toolbar" title="Translate">
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentIsMask} url="nylas://composer-translate/assets/translate-icon@2x.png" />
|
||||
<span style={fontSize: "9px", verticalAlign: "top"}>▼</span>
|
||||
<RetinaImg mode={RetinaImg.Mode.ContentIsMask} url="nylas://composer-translate/assets/icon-composer-translate@2x.png" />
|
||||
|
||||
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
_onTranslate: (lang) =>
|
||||
|
|
|
@ -8,8 +8,12 @@ class SendActionButton extends React.Component
|
|||
|
||||
@propTypes:
|
||||
draft: React.PropTypes.object
|
||||
style: React.PropTypes.object
|
||||
isValidDraft: React.PropTypes.func
|
||||
|
||||
@defaultProps:
|
||||
style: {}
|
||||
|
||||
@CONFIG_KEY: "core.sending.defaultSendType"
|
||||
|
||||
constructor: (@props) ->
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
@compose-width: 800px;
|
||||
@compose-min-height: 150px;
|
||||
|
||||
@blurred-bg-color: mix(@background-primary, #ffbb00, 96%);
|
||||
@blurred-primary-color: mix(@background-primary, #ffbb00, 96%);
|
||||
@blurred-off-primary-color: mix(@background-off-primary, #ffbb00, 96%);
|
||||
|
||||
body.platform-win32 {
|
||||
.composer-inner-wrap {
|
||||
.composer-drop-cover {
|
||||
border-radius: 0;
|
||||
}
|
||||
.composer-action-bar-wrap {
|
||||
border-radius: 0;
|
||||
}
|
||||
input, input:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -50,20 +54,45 @@ body.platform-win32 {
|
|||
.composer-action-bar-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
background: @background-off-primary;
|
||||
border-top: 1px solid darken(@background-off-primary, 7%);
|
||||
box-shadow: inset 0 2px 1px rgba(0,0,0,0.03);
|
||||
border-bottom: 0;
|
||||
border-radius: @border-radius-base;
|
||||
|
||||
// Buttons in the composer footer
|
||||
.btn.btn-toolbar:not(.btn-emphasis) {
|
||||
background: transparent;
|
||||
box-shadow: 0 0 0;
|
||||
margin: 0;
|
||||
padding: 0 9px;
|
||||
|
||||
img.content-mask {
|
||||
background-color: fadeout(mix(@btn-default-text-color, @component-active-color, 88%), 30%);
|
||||
}
|
||||
&:hover {
|
||||
img.content-mask {
|
||||
background-color: fadeout(mix(@btn-default-text-color, @component-active-color, 88%), 5%);
|
||||
}
|
||||
}
|
||||
&.btn-enabled {
|
||||
color: @component-active-color;
|
||||
img.content-mask {
|
||||
background-color: @component-active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-send {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.composer-action-bar-content {
|
||||
display:flex;
|
||||
margin: 0 auto;
|
||||
flex-direction:row;
|
||||
max-width: @compose-width;
|
||||
padding: @spacing-standard;
|
||||
|
||||
> * {
|
||||
margin-left: @spacing-standard / 2;
|
||||
margin-right: @spacing-standard / 2;
|
||||
}
|
||||
padding: 9px 22.5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -254,7 +283,7 @@ body.platform-win32 {
|
|||
}
|
||||
|
||||
.composer-action-bar-content {
|
||||
padding: 8px 0.5px;
|
||||
padding: 9px 13.5px 9px 22.5px;
|
||||
}
|
||||
|
||||
.compose-body {
|
||||
|
@ -281,9 +310,16 @@ body.platform-win32 {
|
|||
#message-list {
|
||||
.message-item-wrap {
|
||||
.message-item-white-wrap.composer-outer-wrap {
|
||||
background: @blurred-bg-color;
|
||||
background: @blurred-primary-color;
|
||||
|
||||
.btn.btn-toolbar.btn-trash {
|
||||
padding-right: 0;
|
||||
}
|
||||
.show-more-fade {
|
||||
background: linear-gradient(to right, fade(@blurred-bg-color, 0%) 0%, fade(@blurred-bg-color, 100%) 40%);
|
||||
background: linear-gradient(to right, fade(@blurred-primary-color, 0%) 0%, fade(@blurred-primary-color, 100%) 40%);
|
||||
}
|
||||
.composer-action-bar-wrap {
|
||||
background: @blurred-off-primary-color;
|
||||
}
|
||||
}
|
||||
.message-item-white-wrap.composer-outer-wrap.focused {
|
||||
|
@ -292,6 +328,9 @@ body.platform-win32 {
|
|||
.show-more-fade {
|
||||
background: linear-gradient(to right, fade(@background-primary, 0%) 0%, fade(@background-primary, 100%) 40%);
|
||||
}
|
||||
.composer-action-bar-wrap {
|
||||
background: @background-off-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,10 +44,11 @@ export default class LinkTrackingButton extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const title = this.state.enabled ? "Disable" : "Enable"
|
||||
return (
|
||||
<button
|
||||
title="Link Tracking"
|
||||
className={`btn btn-toolbar ${this.state.enabled ? "btn-action" : ""}`}
|
||||
title={`${title} link tracking`}
|
||||
className={`btn btn-toolbar ${this.state.enabled ? "btn-enabled" : ""}`}
|
||||
onClick={this._onClick}>
|
||||
<RetinaImg
|
||||
url="nylas://link-tracking/assets/linktracking-icon@2x.png"
|
||||
|
|
|
@ -41,7 +41,9 @@ class InitialPackagesPage extends React.Component
|
|||
<div>
|
||||
{@state.packages.map (item) =>
|
||||
<div className="initial-package" key={item.name}>
|
||||
<img src="nylas://#{item.name}/#{item.icon}" style={width:50} />
|
||||
<div className="icon-container">
|
||||
<img src="nylas://#{item.name}/#{item.icon}" style={width:27, alignContent: 'center', objectFit: 'scale-down'} />
|
||||
</div>
|
||||
<div className="install-container">
|
||||
<InstallButton package={item} />
|
||||
</div>
|
||||
|
|
|
@ -394,8 +394,17 @@
|
|||
border:1px solid #e1e1e1;
|
||||
text-align:left;
|
||||
cursor: default;
|
||||
img {
|
||||
|
||||
.icon-container {
|
||||
float:left;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 6px;
|
||||
background: linear-gradient(to bottom, @background-primary 0%, @background-secondary 100%);
|
||||
box-shadow: 0 0.5px 0 rgba(0,0,0,0.15), 0 -0.5px 0 rgba(0,0,0,0.15), 0.5px 0 0 rgba(0,0,0,0.15), -0.5px 0 0 rgba(0,0,0,0.15), 0 0.5px 1px rgba(0, 0, 0, 0.15);
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
margin: 12px;
|
||||
margin-left: 5px;
|
||||
margin-right: 20px;
|
||||
|
|
Before Width: | Height: | Size: 519 B |
Before Width: | Height: | Size: 638 B |
BIN
internal_packages/open-tracking/assets/icon-composer-eye@1x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
internal_packages/open-tracking/assets/icon-composer-eye@2x.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
|
@ -45,9 +45,10 @@ export default class OpenTrackingButton extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
return (<button className={`btn btn-toolbar ${this.state.enabled ? "btn-action" : ""}`}
|
||||
onClick={this._onClick} title="Open Tracking">
|
||||
<RetinaImg url="nylas://open-tracking/assets/envelope-open-icon@2x.png"
|
||||
const title = this.state.enabled ? "Disable" : "Enable";
|
||||
return (<button className={`btn btn-toolbar ${this.state.enabled ? "btn-enabled" : ""}`}
|
||||
onClick={this._onClick} title={`${title} read receipts`}>
|
||||
<RetinaImg url="nylas://open-tracking/assets/icon-composer-eye@2x.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</button>)
|
||||
}
|
||||
|
|
|
@ -27,17 +27,11 @@ export default class OpenTrackingIcon extends React.Component {
|
|||
_renderIcon = () => {
|
||||
if (this.state.opened == null) {
|
||||
return <span />;
|
||||
} else if (this.state.opened) {
|
||||
return (
|
||||
<RetinaImg
|
||||
url="nylas://open-tracking/assets/envelope-open-icon@2x.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<RetinaImg
|
||||
className="unopened"
|
||||
url="nylas://open-tracking/assets/envelope-closed-icon@2x.png"
|
||||
className={this.state.opened ? "opened" : "unopened"}
|
||||
url="nylas://open-tracking/assets/icon-composer-eye@2x.png"
|
||||
mode={RetinaImg.Mode.ContentIsMask} />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
|
||||
.preferences-sidebar {
|
||||
background: @background-secondary;
|
||||
background-color: @source-list-bg;
|
||||
border-right: 1px solid @border-color-divider;
|
||||
flex: 1;
|
||||
max-width:350px;
|
||||
|
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -5,8 +5,8 @@ class CalendarButton extends React.Component
|
|||
@displayName: 'CalendarButton'
|
||||
|
||||
render: =>
|
||||
<button className="btn btn-toolbar" onClick={@_onClick} title="QuickSchedule">
|
||||
<RetinaImg url="nylas://quick-schedule/assets/quickschedule-icon@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
<button className="btn btn-toolbar" onClick={@_onClick} title="Quick schedule">
|
||||
<RetinaImg url="nylas://quick-schedule/assets/icon-composer-quickschedule@2x.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</button>
|
||||
|
||||
_onClick: =>
|
||||
|
|
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 184 KiB |
Before Width: | Height: | Size: 1.6 MiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,8 +1,9 @@
|
|||
/** @babel */
|
||||
import _ from 'underscore'
|
||||
import Rx from 'rx-lite'
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import {DateUtils} from 'nylas-exports'
|
||||
import {Popover} from 'nylas-component-kit'
|
||||
import {DateUtils, Message, DatabaseStore} from 'nylas-exports'
|
||||
import {Popover, RetinaImg, Menu} from 'nylas-component-kit'
|
||||
import SendLaterActions from './send-later-actions'
|
||||
import SendLaterStore from './send-later-store'
|
||||
import {DATE_FORMAT_SHORT, DATE_FORMAT_LONG} from './send-later-constants'
|
||||
|
@ -10,6 +11,7 @@ import {DATE_FORMAT_SHORT, DATE_FORMAT_LONG} from './send-later-constants'
|
|||
|
||||
const SendLaterOptions = {
|
||||
'In 1 hour': DateUtils.in1Hour,
|
||||
'In 2 hours': DateUtils.in2Hours,
|
||||
'Later Today': DateUtils.laterToday,
|
||||
'Tomorrow Morning': DateUtils.tomorrow,
|
||||
'Tomorrow Evening': DateUtils.tomorrowEvening,
|
||||
|
@ -27,132 +29,143 @@ class SendLaterPopover extends Component {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
inputSendDate: null,
|
||||
isScheduled: SendLaterStore.isScheduled(this.props.draftClientId),
|
||||
inputDate: null,
|
||||
scheduledDate: null,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.unsubscribe = SendLaterStore.listen(this.onScheduledMessagesChanged)
|
||||
this._subscription = Rx.Observable.fromQuery(
|
||||
DatabaseStore.findBy(Message, {clientId: this.props.draftClientId})
|
||||
).subscribe((draft)=> {
|
||||
const scheduledDate = SendLaterStore.getScheduledDateForMessage(draft);
|
||||
if (scheduledDate !== this.state.scheduledDate) {
|
||||
this.setState({scheduledDate});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe()
|
||||
this._subscription.dispose();
|
||||
}
|
||||
|
||||
onSendLater = (momentDate)=> {
|
||||
const utcDate = momentDate.utc()
|
||||
const formatted = DateUtils.format(utcDate)
|
||||
SendLaterActions.sendLater(this.props.draftClientId, formatted)
|
||||
onSelectMenuOption = (optionKey)=> {
|
||||
const date = SendLaterOptions[optionKey]();
|
||||
const formatted = DateUtils.format(date.utc())
|
||||
|
||||
this.setState({isScheduled: null, inputSendDate: null})
|
||||
SendLaterActions.sendLater(this.props.draftClientId, formatted)
|
||||
this.setState({scheduledDate: 'saving', inputDate: null})
|
||||
this.refs.popover.close()
|
||||
};
|
||||
|
||||
onCancelSendLater = ()=> {
|
||||
SendLaterActions.cancelSendLater(this.props.draftClientId)
|
||||
this.setState({inputSendDate: null})
|
||||
this.setState({inputDate: null})
|
||||
this.refs.popover.close()
|
||||
};
|
||||
|
||||
onScheduledMessagesChanged = ()=> {
|
||||
const isScheduled = SendLaterStore.isScheduled(this.props.draftClientId)
|
||||
if (isScheduled !== this.state.isScheduled) {
|
||||
this.setState({isScheduled});
|
||||
renderCustomTimeSection() {
|
||||
const updateInputDateValue = _.debounce((value)=> {
|
||||
this.setState({inputDate: DateUtils.fromString(value)})
|
||||
}, 250);
|
||||
|
||||
let dateInterpretation = false;
|
||||
if (this.state.inputDate) {
|
||||
dateInterpretation = (<em>
|
||||
{DateUtils.format(this.state.inputDate, DATE_FORMAT_LONG)}
|
||||
</em>);
|
||||
}
|
||||
};
|
||||
|
||||
onInputChange = (event)=> {
|
||||
this.updateInputSendDateValue(event.target.value)
|
||||
};
|
||||
return (
|
||||
<div key="custom" className="custom-time-section">
|
||||
<input
|
||||
tabIndex={1}
|
||||
type="text"
|
||||
placeholder="Or type a time..."
|
||||
onChange={event=> updateInputDateValue(event.target.value)}/>
|
||||
{dateInterpretation}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getButtonLabel = (isScheduled)=> {
|
||||
return isScheduled ? '✅ Scheduled' : 'Send Later';
|
||||
};
|
||||
renderMenuOption(optionKey) {
|
||||
const date = SendLaterOptions[optionKey]();
|
||||
const formatted = DateUtils.format(date, DATE_FORMAT_SHORT);
|
||||
return (
|
||||
<div className="send-later-option">{optionKey}<em>{formatted}</em></div>
|
||||
);
|
||||
}
|
||||
|
||||
updateInputSendDateValue = _.debounce((dateValue)=> {
|
||||
const inputSendDate = DateUtils.fromString(dateValue)
|
||||
this.setState({inputSendDate})
|
||||
}, 250);
|
||||
renderButton() {
|
||||
const {scheduledDate} = this.state;
|
||||
let className = 'btn btn-toolbar btn-send-later';
|
||||
|
||||
renderItems() {
|
||||
return Object.keys(SendLaterOptions).map((label)=> {
|
||||
const date = SendLaterOptions[label]()
|
||||
const formatted = DateUtils.format(date, DATE_FORMAT_SHORT)
|
||||
if (scheduledDate === 'saving') {
|
||||
return (
|
||||
<div
|
||||
key={label}
|
||||
onMouseDown={this.onSendLater.bind(this, date)}
|
||||
className="send-later-option">
|
||||
{label}
|
||||
<em className="item-date-value">{formatted}</em>
|
||||
</div>
|
||||
<button className={className}>
|
||||
<RetinaImg
|
||||
name="inline-loading-spinner.gif"
|
||||
mode={RetinaImg.Mode.ContentDark}
|
||||
style={{width: 14, height: 14}}/>
|
||||
</button>
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
renderEmptyInput() {
|
||||
let dateInterpretation = false;
|
||||
if (scheduledDate) {
|
||||
className += ' btn-enabled';
|
||||
const momentDate = DateUtils.fromString(scheduledDate);
|
||||
if (momentDate) {
|
||||
dateInterpretation = <span className="at">Sending in {momentDate.fromNow(true)}</span>;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="send-later-section">
|
||||
<label>At a specific time</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Next Monday at 1pm"
|
||||
onChange={this.onInputChange}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderLabeledInput(inputSendDate) {
|
||||
const formatted = DateUtils.format(inputSendDate, DATE_FORMAT_LONG)
|
||||
return (
|
||||
<div className="send-later-section">
|
||||
<label>At a specific time</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. Next Monday at 1pm"
|
||||
onChange={this.onInputChange}/>
|
||||
<em className="input-date-value">{formatted}</em>
|
||||
<button
|
||||
className="btn btn-send-later"
|
||||
onClick={this.onSendLater.bind(this, inputSendDate)}>Schedule Email</button>
|
||||
</div>
|
||||
)
|
||||
<button className={className}>
|
||||
<RetinaImg name="icon-composer-sendlater.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
{dateInterpretation}
|
||||
<span data-reactid=".s.0.0.1"> </span>
|
||||
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isScheduled, inputSendDate} = this.state
|
||||
const buttonLabel = isScheduled != null ? this.getButtonLabel(isScheduled) : 'Scheduling...';
|
||||
const button = (
|
||||
<button className="btn btn-primary send-later-button">{buttonLabel}</button>
|
||||
)
|
||||
const input = inputSendDate ? this.renderLabeledInput(inputSendDate) : this.renderEmptyInput();
|
||||
const footerComponents = [
|
||||
<div key="divider" className="divider" />,
|
||||
this.renderCustomTimeSection(),
|
||||
];
|
||||
|
||||
if (this.state.scheduledDate) {
|
||||
footerComponents.push(<div key="divider-unschedule" className="divider" />)
|
||||
footerComponents.push(
|
||||
<div className="cancel-section" key="cancel-section">
|
||||
<button className="btn" onClick={this.onCancelSendLater}>
|
||||
Unschedule Send
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
ref="popover"
|
||||
style={{order: -103}}
|
||||
className="send-later"
|
||||
buttonComponent={button}>
|
||||
<div className="send-later-container">
|
||||
{this.renderItems()}
|
||||
<div className="divider" />
|
||||
{input}
|
||||
{isScheduled ?
|
||||
<div className="divider" />
|
||||
: void 0}
|
||||
{isScheduled ?
|
||||
<div className="send-later-section">
|
||||
<button className="btn btn-send-later" onClick={this.onCancelSendLater}>
|
||||
Unschedule Send
|
||||
</button>
|
||||
</div>
|
||||
: void 0}
|
||||
</div>
|
||||
buttonComponent={this.renderButton()}>
|
||||
<Menu items={ Object.keys(SendLaterOptions) }
|
||||
itemKey={ (item)=> item }
|
||||
itemContent={this.renderMenuOption}
|
||||
footerComponents={footerComponents}
|
||||
onSelect={this.onSelectMenuOption}
|
||||
/>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SendLaterPopover.containerStyles = {
|
||||
order: -99,
|
||||
};
|
||||
|
||||
export default SendLaterPopover
|
||||
|
|
|
@ -9,61 +9,39 @@ class SendLaterStore extends NylasStore {
|
|||
|
||||
constructor(pluginId = PLUGIN_ID) {
|
||||
super()
|
||||
this.pluginId = pluginId
|
||||
this.scheduledMessages = new Map()
|
||||
this.pluginId = pluginId;
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.setupQuerySubscription()
|
||||
|
||||
this.unsubscribers = [
|
||||
SendLaterActions.sendLater.listen(this.onSendLater),
|
||||
SendLaterActions.cancelSendLater.listen(this.onCancelSendLater),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
setupQuerySubscription() {
|
||||
const query = DatabaseStore.findAll(
|
||||
Message, [Message.attributes.pluginMetadata.contains(this.pluginId)]
|
||||
)
|
||||
this.queryDisposable = Rx.Observable.fromQuery(query).subscribe(this.onScheduledMessagesChanged)
|
||||
}
|
||||
|
||||
getScheduledMessage = (messageClientId)=> {
|
||||
return this.scheduledMessages.get(messageClientId)
|
||||
};
|
||||
|
||||
isScheduled = (messageClientId)=> {
|
||||
const message = this.getScheduledMessage(messageClientId)
|
||||
if (message && message.metadataForPluginId(this.pluginId).sendLaterDate) {
|
||||
return true
|
||||
getScheduledDateForMessage = (message)=> {
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
return false
|
||||
const metadata = message.metadataForPluginId(this.pluginId) || {};
|
||||
return metadata.sendLaterDate || null;
|
||||
};
|
||||
|
||||
setMetadata = (draftClientId, metadata)=> {
|
||||
return (
|
||||
DatabaseStore.modelify(Message, [draftClientId])
|
||||
.then((messages)=> {
|
||||
const {accountId} = messages[0]
|
||||
return NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId)
|
||||
.then(()=> {
|
||||
Actions.setMetadata(messages, this.pluginId, metadata)
|
||||
})
|
||||
.catch((error)=> {
|
||||
console.error(error)
|
||||
NylasEnv.showErrorDialog(error.message)
|
||||
})
|
||||
const {accountId} = messages[0];
|
||||
return NylasAPI.authPlugin(this.pluginId, PLUGIN_NAME, accountId);
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
onScheduledMessagesChanged = (messages)=> {
|
||||
this.scheduledMessages.clear()
|
||||
messages.forEach((message)=> {
|
||||
this.scheduledMessages.set(message.clientId, message);
|
||||
})
|
||||
this.trigger()
|
||||
.then(()=> {
|
||||
Actions.setMetadata(messages, this.pluginId, metadata);
|
||||
})
|
||||
.catch((error)=> {
|
||||
NylasEnv.reportError(error);
|
||||
NylasEnv.showErrorDialog(`Sorry, we were unable to schedule this message. ${error.message}`);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
onSendLater = (draftClientId, sendLaterDate)=> {
|
||||
|
@ -75,7 +53,6 @@ class SendLaterStore extends NylasStore {
|
|||
};
|
||||
|
||||
deactivate = ()=> {
|
||||
this.queryDisposable.dispose()
|
||||
this.unsubscribers.forEach(unsub => unsub())
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,60 +1,60 @@
|
|||
@import "ui-variables";
|
||||
|
||||
|
||||
.send-later {
|
||||
em {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.send-later-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0;
|
||||
.menu {
|
||||
width: 250px;
|
||||
|
||||
.divider {
|
||||
border-top: 1px solid @border-color-divider;
|
||||
margin: 10px 0;
|
||||
width: 90%;
|
||||
align-self: center;
|
||||
.header-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.send-later-section {
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
input {
|
||||
border: 1px solid @input-border;
|
||||
}
|
||||
.input-date-value {
|
||||
font-size: 0.9em;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.btn-send-later {
|
||||
width: 100%;
|
||||
}
|
||||
.content-container {
|
||||
overflow:hidden;
|
||||
border-top-right-radius: @border-radius-base;
|
||||
border-top-left-radius: @border-radius-base;
|
||||
}
|
||||
|
||||
.send-later-option {
|
||||
cursor: default;
|
||||
width: 100%;
|
||||
padding: 1px 10px;
|
||||
|
||||
.item-date-value {
|
||||
.item {
|
||||
em {
|
||||
display: none;
|
||||
float: right;
|
||||
font-size: 0.9em;
|
||||
padding-right: @padding-base-horizontal;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @background-secondary;
|
||||
.item-date-value {
|
||||
em {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.divider {
|
||||
border-top: 1px solid @border-color-divider;
|
||||
}
|
||||
.custom-time-section {
|
||||
padding: @padding-base-vertical @padding-base-horizontal;
|
||||
em {
|
||||
color: @text-color-subtle;
|
||||
}
|
||||
}
|
||||
.cancel-section {
|
||||
padding: @padding-base-vertical @padding-base-horizontal;
|
||||
padding-bottom: @padding-base-vertical * 1.2;
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-send-later {
|
||||
.at {
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.send-later-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -117,10 +117,10 @@ class EmptyState extends React.Component
|
|||
messageOverride = "Nothing to display."
|
||||
if @state.layoutMode is 'list'
|
||||
ContentComponent = ContentQuotes
|
||||
|
||||
if @state.syncing
|
||||
messageOverride = "Please wait while we prepare your mailbox."
|
||||
|
||||
if FocusedPerspectiveStore.current()?.name is "Inbox"
|
||||
else if FocusedPerspectiveStore.current()?.name is "Inbox"
|
||||
ContentComponent = InboxZero
|
||||
|
||||
classes = classNames
|
||||
|
|
|
@ -145,7 +145,6 @@
|
|||
}
|
||||
|
||||
.draft-icon {
|
||||
margin-top:8px;
|
||||
margin-left:10px;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
|
@ -297,7 +296,7 @@
|
|||
background-size: 100%;
|
||||
zoom:0.5;
|
||||
width: 81px;
|
||||
height: 57px;
|
||||
height: 51px;
|
||||
margin: 9px 16px 0 16px;
|
||||
}
|
||||
.action.action-archive {
|
||||
|
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 54 KiB |
|
@ -1,9 +1,10 @@
|
|||
@import "ui-variables";
|
||||
|
||||
@snooze-img: "../internal_packages/thread-snooze/assets/ic-toolbar-native-snooze@2x.png";
|
||||
@snooze-quickaction-img: "../internal_packages/thread-snooze/assets/ic-quickaction-snooze@2x.png";
|
||||
|
||||
.thread-list .list-item .list-column-HoverActions .action.action-snooze {
|
||||
background: url(@snooze-img) center no-repeat, @background-gradient;
|
||||
background-size: 50%;
|
||||
background: url(@snooze-quickaction-img) center no-repeat, @background-gradient;
|
||||
}
|
||||
|
||||
.snooze-popover {
|
||||
|
|
|
@ -80,6 +80,13 @@ class ListTabular extends React.Component
|
|||
idx: previousIdx
|
||||
end: Date.now() + 125
|
||||
|
||||
# If we think /all/ the items are animating out, or a lot of them,
|
||||
# the user probably switched to an entirely different perspective.
|
||||
# Don't bother trying to animate.
|
||||
animatingCount = Object.keys(animatingOut).length
|
||||
if animatingCount > 8 or animatingCount is Object.keys(@state.items).length
|
||||
animatingOut = {}
|
||||
|
||||
renderedRangeStart: start
|
||||
renderedRangeEnd: end
|
||||
count: dataSource.count()
|
||||
|
|
|
@ -45,6 +45,10 @@ const DateUtils = {
|
|||
return DateUtils.minutesFromNow(60);
|
||||
},
|
||||
|
||||
in2Hours() {
|
||||
return DateUtils.minutesFromNow(120);
|
||||
},
|
||||
|
||||
laterToday(now = moment()) {
|
||||
return now.add(3, 'hours').oclock();
|
||||
},
|
||||
|
|
|
@ -140,6 +140,7 @@ body.platform-win32 {
|
|||
&.narrow {
|
||||
padding: 0 9px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
|
|
|
@ -11,18 +11,6 @@
|
|||
border-bottom: 1px solid @base-border-color;
|
||||
padding: 3px;
|
||||
position: relative;
|
||||
input.search {
|
||||
border: 1px solid darken(@background-secondary, 10%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0;
|
||||
background-color: white;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../static/images/search/searchloupe@2x.png");
|
||||
background-size: 15px 15px;
|
||||
background-position: 7px 4px;
|
||||
text-indent: 31px;
|
||||
box-shadow: inset 0 1px 0 rgba(0,0,0,0.05), 0 1px 0 rgba(0,0,0,0.05)
|
||||
}
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
|
@ -100,7 +88,6 @@
|
|||
.item:not(.active):not(.selected):hover {
|
||||
text-decoration: none;
|
||||
background-color: fade(@black, 3%);
|
||||
color:inherit;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
}
|
||||
|
||||
.popover {
|
||||
background-color: @background-primary;
|
||||
background-color: @background-secondary;
|
||||
border-radius: @border-radius-base;
|
||||
box-shadow: 0 0.5px 0 rgba(0, 0, 0, 0.15), 0 -0.5px 0 rgba(0, 0, 0, 0.15), 0.5px 0 0 rgba(0, 0, 0, 0.15), -0.5px 0 0 rgba(0, 0, 0, 0.15), 0 4px 7px rgba(0,0,0,0.15);
|
||||
.menu {
|
||||
|
@ -30,11 +30,28 @@
|
|||
border-bottom-right-radius: @border-radius-base;
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
border: 1px solid darken(@background-secondary, 10%);
|
||||
border-radius: 3px;
|
||||
background-color: @background-primary;
|
||||
box-shadow: inset 0 1px 0 rgba(0,0,0,0.05), 0 1px 0 rgba(0,0,0,0.05);
|
||||
color: @text-color;
|
||||
|
||||
&.search {
|
||||
padding-left: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../static/images/search/searchloupe@2x.png");
|
||||
background-size: 15px 15px;
|
||||
background-position: 7px 4px;
|
||||
text-indent: 31px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-pointer {
|
||||
-webkit-mask-image: url('images/tooltip/tooltip-bg-pointer@2x.png');
|
||||
background-color: @background-primary;
|
||||
background-color: @background-secondary;
|
||||
}
|
||||
.popover-pointer.shadow {
|
||||
-webkit-mask-image: url('images/tooltip/tooltip-bg-pointer-shadow@2x.png');
|
||||
|
|
BIN
static/images/composer/icon-composer-attachment@1x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 1.7 KiB |
BIN
static/images/composer/icon-composer-dropdown@1x.png
Normal file
After Width: | Height: | Size: 992 B |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 1.1 KiB |
BIN
static/images/composer/icon-composer-eye@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
static/images/composer/icon-composer-overflow@1x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
static/images/composer/icon-composer-overflow@2x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
static/images/composer/icon-composer-sendlater@2x.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
static/images/composer/icon-composer-trash@1x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.4 KiB |