diff --git a/internal_packages/account-sidebar/lib/account-sidebar-sheet-item.cjsx b/internal_packages/account-sidebar/lib/account-sidebar-sheet-item.cjsx index d6b289677..c9af9d154 100644 --- a/internal_packages/account-sidebar/lib/account-sidebar-sheet-item.cjsx +++ b/internal_packages/account-sidebar/lib/account-sidebar-sheet-item.cjsx @@ -1,4 +1,5 @@ React = require 'react' +_ = require 'underscore-plus' classNames = require 'classnames' {Actions, Utils, WorkspaceStore} = require 'inbox-exports' {RetinaImg} = require 'ui-components' @@ -7,12 +8,22 @@ class AccountSidebarSheetItem extends React.Component @displayName: 'AccountSidebarSheetItem' render: => - classSet = classNames + classSet = classNames 'item': true 'selected': @props.select + if @props.item.icon and @props.item.icon.displayName? + component = @props.item.icon + icon = + + else if _.isString(@props.item.icon) + icon = + + else + icon = +
- + {icon} {@props.item.name}
diff --git a/internal_packages/account-sidebar/lib/account-sidebar-store.coffee b/internal_packages/account-sidebar/lib/account-sidebar-store.coffee index 2614bb334..c2214127a 100644 --- a/internal_packages/account-sidebar/lib/account-sidebar-store.coffee +++ b/internal_packages/account-sidebar/lib/account-sidebar-store.coffee @@ -11,9 +11,13 @@ _ = require 'underscore-plus' AccountSidebarStore = Reflux.createStore init: -> + @_inboxCount = null + @_tags = [] + @_setStoreDefaults() @_registerListeners() @_populate() + @_populateInboxCount() ########### PUBLIC ##################################################### @@ -42,55 +46,63 @@ AccountSidebarStore = Reflux.createStore return unless namespace DatabaseStore.findAll(Tag, namespaceId: namespace.id).then (tags) => - # Collect the built-in tags we want to display, and the user tags - # (which can be identified by having non-hardcoded IDs) - - # We ignore the server drafts so we can use our own localDrafts - tags = _.reject tags, (tag) -> tag.id is "drafts" - - # We ignore the trash tag because you can't trash anything - tags = _.reject tags, (tag) -> tag.id is "trash" - - mainTagIDs = ['inbox', 'drafts', 'sent', 'archive'] - mainTags = _.filter tags, (tag) -> _.contains(mainTagIDs, tag.id) - userTags = _.reject tags, (tag) -> _.contains(mainTagIDs, tag.id) - - # Sort the main tags so they always appear in a standard order - mainTags = _.sortBy mainTags, (tag) -> mainTagIDs.indexOf(tag.id) - mainTags.push new Tag(name: 'All Mail', id: '*') - - # Sort user tags by name - userTags = _.sortBy(userTags, 'name') - - # Find root views, add the Views section - rootSheets = _.filter WorkspaceStore.Sheet, (sheet) -> sheet.root and sheet.name - - lastSections = @_sections - @_sections = [ - { label: 'Mailboxes', items: mainTags, type: 'tag' }, - { label: 'Views', items: rootSheets, type: 'sheet' }, - { label: 'Tags', items: userTags, type: 'tag' }, - ] - - @trigger(@) + @_tags = tags + @_build() _populateInboxCount: -> namespace = NamespaceStore.current() return unless namespace - # Make a web request for unread count - atom.inbox.makeRequest - method: 'GET' - path: "/n/#{namespace.id}/tags/inbox" - returnsModel: true + DatabaseStore.count(Thread, [ + Thread.attributes.namespaceId.equal(namespace.id), + Thread.attributes.unread.equal(true), + Thread.attributes.tags.contains('inbox') + ]).then (count) => + if count isnt @_inboxCount + @_inboxCount = count + @_build() - _populateDraftCount: -> - namespace = NamespaceStore.current() - return unless namespace + _build: -> + tags = @_tags - DatabaseStore.count(Message, draft: true).then (count) => - #TODO: Save Draft Count - @trigger(@) + # Collect the built-in tags we want to display, and the user tags + # (which can be identified by having non-hardcoded IDs) + + # We ignore the server drafts so we can use our own localDrafts + tags = _.reject tags, (tag) -> tag.id is "drafts" + + # We ignore the trash tag because you can't trash anything + tags = _.reject tags, (tag) -> tag.id is "trash" + + mainTagIDs = ['inbox', 'drafts', 'sent', 'archive'] + mainTags = _.filter tags, (tag) -> _.contains(mainTagIDs, tag.id) + userTags = _.reject tags, (tag) -> _.contains(mainTagIDs, tag.id) + + # Sort the main tags so they always appear in a standard order + mainTags = _.sortBy mainTags, (tag) -> mainTagIDs.indexOf(tag.id) + mainTags.push new Tag(name: 'All Mail', id: '*') + + inboxTag = _.find tags, (tag) -> tag.id is 'inbox' + inboxTag?.unreadCount = @_inboxCount + + # Sort user tags by name + userTags = _.sortBy(userTags, 'name') + + # Find root views, add the Views section + featureSheets = _.filter WorkspaceStore.Sheet, (sheet) -> + sheet.name in ['Today'] + extraSheets = _.filter WorkspaceStore.Sheet, (sheet) -> + sheet.root and sheet.name and not (sheet in featureSheets) + + lastSections = @_sections + @_sections = [ + { label: '', items: featureSheets, type: 'sheet' }, + { label: 'Mailboxes', items: mainTags, type: 'tag' }, + { label: 'Views', items: extraSheets, type: 'sheet' }, + { label: 'Tags', items: userTags, type: 'tag' }, + ] + + @trigger(@) _refetchFromAPI: -> namespace = NamespaceStore.current() @@ -113,17 +125,11 @@ AccountSidebarStore = Reflux.createStore _onDataChanged: (change) -> @populateInboxCountDebounced ?= _.debounce => @_populateInboxCount() - , 1000 - @populateDraftCountDebounced ?= _.debounce => - @_populateDraftCount() - , 1000 + , 5000 if change.objectClass is Tag.name @_populate() if change.objectClass is Thread.name @populateInboxCountDebounced() - if change.objectClass is Message.name - return unless _.some change.objects, (msg) -> msg.draft - @populateDraftCountDebounced() module.exports = AccountSidebarStore diff --git a/internal_packages/inbox-activity-bar/lib/activity-bar-store.coffee b/internal_packages/inbox-activity-bar/lib/activity-bar-store.coffee index aa291daa3..d907bcbe8 100644 --- a/internal_packages/inbox-activity-bar/lib/activity-bar-store.coffee +++ b/internal_packages/inbox-activity-bar/lib/activity-bar-store.coffee @@ -24,7 +24,7 @@ ActivityBarStore = Reflux.createStore @_curlHistory = [] @_longPollHistory = [] @_longPollState = 'Unknown' - @_visible = false + @_visible = atom.inDevMode() _registerListeners: -> @listenTo Actions.didMakeAPIRequest, @_onAPIRequest diff --git a/internal_packages/message-list/lib/email-frame.cjsx b/internal_packages/message-list/lib/email-frame.cjsx index 18003fbb0..0338eb2c9 100644 --- a/internal_packages/message-list/lib/email-frame.cjsx +++ b/internal_packages/message-list/lib/email-frame.cjsx @@ -93,12 +93,14 @@ EmailFixingStyles = """ .gmail_extra, .gmail_quote, + #divRplyFwdMsg, blockquote { display:none; } .show-quoted-text .gmail_extra, .show-quoted-text .gmail_quote, + .show-quoted-text #divRplyFwdMsg, .show-quoted-text blockquote { display:inherit; } @@ -160,4 +162,4 @@ class EmailFrame extends React.Component Utils.stripQuotedText(email) -module.exports = EmailFrame \ No newline at end of file +module.exports = EmailFrame diff --git a/internal_packages/onboarding/lib/container-view.cjsx b/internal_packages/onboarding/lib/container-view.cjsx index 44e5c8978..f03789e38 100644 --- a/internal_packages/onboarding/lib/container-view.cjsx +++ b/internal_packages/onboarding/lib/container-view.cjsx @@ -31,6 +31,8 @@ class ContainerView extends React.Component if webview node = React.findDOMNode(webview) if node.hasListeners is undefined + node.addEventListener 'new-window', (e) -> + require('shell').openExternal(e.url) node.addEventListener 'did-start-loading', (e) -> if node.hasMobileUserAgent is undefined node.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 7_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D167 Safari/9537.53") diff --git a/internal_packages/today/assets/HurmeGeometricSans4Thin.otf b/internal_packages/today/assets/HurmeGeometricSans4Thin.otf new file mode 100755 index 000000000..cdf86a416 Binary files /dev/null and b/internal_packages/today/assets/HurmeGeometricSans4Thin.otf differ diff --git a/internal_packages/today/assets/background.png b/internal_packages/today/assets/background.png new file mode 100644 index 000000000..d4b0c4b82 Binary files /dev/null and b/internal_packages/today/assets/background.png differ diff --git a/internal_packages/today/lib/main.cjsx b/internal_packages/today/lib/main.cjsx new file mode 100644 index 000000000..ba07d2c81 --- /dev/null +++ b/internal_packages/today/lib/main.cjsx @@ -0,0 +1,16 @@ +TodayView = require "./today-view" +TodayIcon = require "./today-icon" +{ComponentRegistry, + WorkspaceStore} = require 'inbox-exports' + +module.exports = + + activate: (@state={}) -> + WorkspaceStore.defineSheet 'Today', {root: true, supportedModes: ['list'], name: 'Today', icon: TodayIcon}, + list: ['RootSidebar', 'Today'] + + ComponentRegistry.register TodayView, + location: WorkspaceStore.Location.Today + + deactivate: -> + ComponentRegistry.unregister(TodayView) diff --git a/internal_packages/today/lib/today-icon.cjsx b/internal_packages/today/lib/today-icon.cjsx new file mode 100644 index 000000000..10ba46203 --- /dev/null +++ b/internal_packages/today/lib/today-icon.cjsx @@ -0,0 +1,32 @@ +React = require 'react' +_ = require "underscore-plus" +moment = require 'moment' +classNames = require 'classnames' + +class TodayIcon extends React.Component + @displayName: 'TodayIcon' + + constructor: (@props) -> + @state = + moment: moment() + + componentDidMount: => + @_setTimeState() + + componentWillUnmount: => + clearInterval(@_timer) + + render: => + classes = classNames + 'today-icon': true + 'selected': @props.selected + +
{@state.moment.format('D')}
+ + _setTimeState: => + timeTillNextSecond = (60 - (new Date).getSeconds()) * 1000 + @_timer = setTimeout(@_setTimeState, timeTillNextSecond) + @setState(moment: moment()) + + +module.exports = TodayIcon diff --git a/internal_packages/today/lib/today-view.cjsx b/internal_packages/today/lib/today-view.cjsx new file mode 100644 index 000000000..bacbfb68a --- /dev/null +++ b/internal_packages/today/lib/today-view.cjsx @@ -0,0 +1,83 @@ +React = require 'react' +_ = require "underscore-plus" +{Utils, Actions} = require 'inbox-exports' +{Spinner, EventedIFrame} = require 'ui-components' +moment = require 'moment' + +class TodayViewDateTime extends React.Component + @displayName: 'TodayViewDateTime' + + constructor: (@props) -> + @state = + moment: moment() + + componentDidMount: => + @_setTimeState() + + componentWillUnmount: => + clearInterval(@_timer) + + render: => +
+
{@state.moment.format('h:mm')}
+
{@state.moment.format('dddd, MMM Do')}
+
+ + _setTimeState: => + timeTillNextSecond = (60 - (new Date).getSeconds()) * 1000 + @_timer = setTimeout(@_setTimeState, timeTillNextSecond) + + @setState(moment: moment()) + + +class TodayViewBox extends React.Component + @displayName: 'TodayViewBox' + + @propTypes: + name: React.PropTypes.string.isRequired + + constructor: (@props) -> + + render: => +
+

{@props.name}

+
+ +class TodayView extends React.Component + @displayName: 'TodayView' + + constructor: (@props) -> + @state = @_getStateFromStores() + + render: => +
+
+ +
+ + + + + + +
+
+ Inbox +
+
+
+ + componentDidMount: => + @_unsubscribers = [] + + componentWillUnmount: => + unsubscribe() for unsubscribe in @_unsubscribers + + _getStateFromStores: => + {} + + _onChange: => + @setState(@_getStateFromStores()) + + +module.exports = TodayView diff --git a/internal_packages/today/package.json b/internal_packages/today/package.json new file mode 100755 index 000000000..0a0593879 --- /dev/null +++ b/internal_packages/today/package.json @@ -0,0 +1,14 @@ +{ + "name": "today", + "version": "0.1.0", + "main": "./lib/main", + "description": "Today View", + "license": "Proprietary", + "private": true, + "engines": { + "atom": "*" + }, + "dependencies": { + "moment": "^2.8" + } +} diff --git a/internal_packages/today/stylesheets/today-view.less b/internal_packages/today/stylesheets/today-view.less new file mode 100644 index 000000000..5c91bfe13 --- /dev/null +++ b/internal_packages/today/stylesheets/today-view.less @@ -0,0 +1,86 @@ +@import "ui-variables"; +@import "ui-mixins"; + +@font-face { + font-family: 'Hurme'; + font-style: normal; + src: url(nylas://today/assets/HurmeGeometricSans4Thin.otf); +} + +.today-icon { + display:inline-block; + overflow:hidden; + width:16px; + height:16px; + color:@source-list-bg; + text-align:center; + font-weight:500; + font-size:11px; + line-height:19px; + position:relative; + top:5px; + background-color:@text-color-very-subtle; + + &.selected { + background-color:@accent-primary; + } +} +.today { + background:url(nylas://today/assets/background.png) top center no-repeat; + background-size:100%; + overflow-y:scroll; + position:absolute; + width:100%; + height:100%; + + .inner { + + } + + .to-the-inbox { + opacity:0.3; + position:absolute; + width:100%; + text-align:center; + bottom:10px; + font-weight:@font-weight-semi-bold; + } + .centered { + text-align:center; + opacity:0.6; + .time { + font-family: 'Hurme'; + margin-top:70px; + font-size:100px; + line-height:96px; + } + .date { + font-family:@font-family-sans-serif; + font-weight:@font-weight-normal; + font-size:22px; + } + } + .boxes { + display: flex; + flex-direction:row; + padding:15px; + position:absolute; + bottom:20px; + width:100%; + .box { + margin:15px; + border-radius: @border-radius-large; + background-color:white; + box-shadow: 0 1px 2px rgba(0,0,0,0.3); + flex:1; + height:40vh; + h2 { + margin-top:4px; + padding:12px; + border-bottom:1px solid #ccc; + font-size:15px; + font-weight:@font-weight-semi-bold; + } + } + } +} diff --git a/internal_packages/unread-badge/lib/unread-badge-store.coffee b/internal_packages/unread-badge/lib/unread-badge-store.coffee index 2a679b1f2..c18cb4975 100644 --- a/internal_packages/unread-badge/lib/unread-badge-store.coffee +++ b/internal_packages/unread-badge/lib/unread-badge-store.coffee @@ -1,27 +1,40 @@ Reflux = require 'reflux' _ = require 'underscore-plus' -{DatabaseStore, NamespaceStore, Actions, Tag} = require 'inbox-exports' +{DatabaseStore, NamespaceStore, Actions, Thread} = require 'inbox-exports' remote = require 'remote' app = remote.require 'app' +AppUnreadCount = null + module.exports = AppUnreadBadgeStore = Reflux.createStore init: -> @listenTo NamespaceStore, @_onNamespaceChanged @listenTo DatabaseStore, @_onDataChanged + @_fetchCount() _onNamespaceChanged: -> @_onDataChanged() _onDataChanged: (change) -> - return if change && change.objectClass != Tag.name return app.dock?.setBadge?("") unless NamespaceStore.current() - @_updateBadge() - _updateBadge: -> - DatabaseStore.find(Tag, 'inbox').then (inbox) -> - return unless inbox - count = inbox.unreadCount + if change && change.objectClass is Thread.name + @_fetchCountDebounced ?= _.debounce(@_fetchCount, 5000) + @_fetchCountDebounced() + + _fetchCount: -> + namespace = NamespaceStore.current() + return unless namespace + + DatabaseStore.count(Thread, [ + Thread.attributes.namespaceId.equal(namespace.id), + Thread.attributes.unread.equal(true), + Thread.attributes.tags.contains('inbox') + ]).then (count) -> + return if AppUnreadCount is count + AppUnreadCount = count + if count > 999 app.dock?.setBadge?("\u221E") else if count > 0 diff --git a/src/components/multiselect-list.cjsx b/src/components/multiselect-list.cjsx index c3250425a..d9c6e01b2 100644 --- a/src/components/multiselect-list.cjsx +++ b/src/components/multiselect-list.cjsx @@ -68,6 +68,7 @@ class MultiselectList extends React.Component 'core:previous-item': => @_onShift(-1) 'core:select-down': => @_onShift(1, {select: true}) 'core:select-up': => @_onShift(-1, {select: true}) + 'application:pop-sheet': => @_onDeselect() Object.keys(props.commands).forEach (key) => commands[key] = => @@ -156,6 +157,10 @@ class MultiselectList extends React.Component return unless id @state.dataView.selection.toggle(@state.dataView.getById(id)) + _onDeselect: => + return unless @_visible() + @state.dataView.selection.clear() + _onShift: (delta, options = {}) => if @state.showKeyboardCursor and @_visible() id = @state.keyboardCursorId diff --git a/src/components/resizable-region.cjsx b/src/components/resizable-region.cjsx index d5f8fa3a8..9ae44ec0e 100644 --- a/src/components/resizable-region.cjsx +++ b/src/components/resizable-region.cjsx @@ -35,7 +35,7 @@ class ResizableRegion extends React.Component ### Public: React `props` supported by ResizableRegion: - + - `handle` Provide a {ResizableHandle} to indicate which edge of the region should be draggable. - `onResize` A {Function} that will be called continuously as the region is resized. @@ -58,6 +58,8 @@ class ResizableRegion extends React.Component minHeight: React.PropTypes.number maxHeight: React.PropTypes.number + style: React.PropTypes.object + constructor: (@props = {}) -> @props.handle ?= ResizableHandle.Right @state = @@ -65,7 +67,7 @@ class ResizableRegion extends React.Component render: => if @props.handle.axis is 'horizontal' - containerStyle = + containerStyle = _.extend {}, @props.style, 'minWidth': @props.minWidth 'maxWidth': @props.maxWidth 'position': 'relative' @@ -76,7 +78,7 @@ class ResizableRegion extends React.Component containerStyle.flex = 1 else - containerStyle = + containerStyle = _.extend {}, @props.style, 'minHeight': @props.minHeight 'maxHeight': @props.maxHeight 'position': 'relative' @@ -90,7 +92,7 @@ class ResizableRegion extends React.Component containerStyle.flex = 1 otherProps = _.omit(@props, _.keys(@constructor.propTypes)) - +
{@props.children}
PriorityUICoordinator.endPriorityTask(@_taskId) if @_taskId @_taskId = null diff --git a/src/flux/models/tag.coffee b/src/flux/models/tag.coffee index d1da908f7..e0249535f 100644 --- a/src/flux/models/tag.coffee +++ b/src/flux/models/tag.coffee @@ -15,8 +15,12 @@ about Tags on the Nylas Platform, read the API documentation for more information about what tags are read-only. `unreadCount`: {AttributeNumber} The number of unread threads with the tag. + Note: This attribute is only available when a single tag is fetched directly + from the Nylas API, not when all tags are listed. `threadCount`: {AttributeNumber} The number of threads with the tag. + Note: This attribute is only available when a single tag is fetched directly + from the Nylas API, not when all tags are listed. ### class Tag extends Model @@ -34,4 +38,4 @@ class Tag extends Model modelKey: 'threadCount' jsonKey: 'thread_count' -module.exports = Tag \ No newline at end of file +module.exports = Tag diff --git a/src/flux/models/utils.coffee b/src/flux/models/utils.coffee index 31a452d83..0e8c1d579 100644 --- a/src/flux/models/utils.coffee +++ b/src/flux/models/utils.coffee @@ -134,7 +134,7 @@ Utils = tableNameForJoin: (primaryKlass, secondaryKlass) -> "#{primaryKlass.name}-#{secondaryKlass.name}" - + imageNamed: (resourcePath, fullname) -> [name, ext] = fullname.split('.') @@ -171,6 +171,7 @@ Utils = /<[br|p][ ]*>[\n]?[ ]*>/i, # HTML lines beginning with > /[\n|>]On .* wrote:[\n|<]/, #On ... wrote: on it's own line /.gmail_quote/ # gmail quote class class + /divRplyFwdMsg/ # outlook? ] for regex in regexs diff --git a/src/flux/stores/workspace-store.coffee b/src/flux/stores/workspace-store.coffee index b917caec6..7ed990556 100644 --- a/src/flux/stores/workspace-store.coffee +++ b/src/flux/stores/workspace-store.coffee @@ -106,7 +106,7 @@ WorkspaceStore = Reflux.createStore ### Managing Sheets ### - + # * `id` {String} The ID of the Sheet being defined. # * `options` {Object} If the sheet should be listed in the left sidebar, # pass `{root: true, name: 'Label'}`. @@ -126,6 +126,7 @@ WorkspaceStore = Reflux.createStore columns: columns supportedModes: Object.keys(columns) + icon: options.icon name: options.name root: options.root diff --git a/src/sheet-container.cjsx b/src/sheet-container.cjsx index 2b38007c3..b82a5aa07 100644 --- a/src/sheet-container.cjsx +++ b/src/sheet-container.cjsx @@ -192,6 +192,7 @@ class SheetContainer extends React.Component
@@ -206,6 +207,7 @@ class SheetContainer extends React.Component
diff --git a/src/sheet.cjsx b/src/sheet.cjsx index aaa06e773..e7cd863cc 100644 --- a/src/sheet.cjsx +++ b/src/sheet.cjsx @@ -63,6 +63,7 @@ class Sheet extends React.Component @props.onColumnSizeChanged(@) } minWidth={minWidth} @@ -76,7 +77,7 @@ class Sheet extends React.Component name={"#{@props.data.id}:#{idx}"} className={"column-#{location.id}"} data-column={idx} - style={flex: 1} + style={flex: 1, height:'100%'} matching={location: location, mode: @state.mode}/> _getStateFromStores: =>