mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-04 10:28:46 +08:00
refactor(package): remove old, unused, or private package. Adds examples
Moved to git@github.com:nylas/N1-private-packages.git Also started an "examples" folder at the root level with an example Github package
This commit is contained in:
parent
3b342b2e20
commit
626a76622c
39 changed files with 0 additions and 1356 deletions
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
1
internal_packages/calendar-bar/.gitignore
vendored
1
internal_packages/calendar-bar/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
node_modules
|
|
@ -1 +0,0 @@
|
|||
# React version of thread list
|
|
@ -1,72 +0,0 @@
|
|||
Reflux = require 'reflux'
|
||||
_ = require 'underscore'
|
||||
{DatabaseStore,
|
||||
AccountStore,
|
||||
Actions,
|
||||
Event,
|
||||
Calendar,
|
||||
NylasAPI} = require 'nylas-exports'
|
||||
moment = require 'moment'
|
||||
|
||||
CalendarBarEventStore = Reflux.createStore
|
||||
init: ->
|
||||
@_setStoreDefaults()
|
||||
@_registerListeners()
|
||||
@_populate()
|
||||
@trigger(@)
|
||||
|
||||
########### PUBLIC #####################################################
|
||||
|
||||
events: ->
|
||||
@_events
|
||||
|
||||
range: ->
|
||||
@_range
|
||||
|
||||
########### PRIVATE ####################################################
|
||||
|
||||
_setStoreDefaults: ->
|
||||
@_events = []
|
||||
|
||||
_registerListeners: ->
|
||||
@listenTo DatabaseStore, @_onDataChanged
|
||||
@listenTo AccountStore, @_onAccountChanged
|
||||
|
||||
_populate: ->
|
||||
@_range =
|
||||
start: moment({hour: 0, milliseconds: -1}).valueOf() / 1000.0
|
||||
end: moment({hour: 24, milliseconds: 1}).valueOf() / 1000.0
|
||||
|
||||
account = AccountStore.current()
|
||||
return unless account
|
||||
|
||||
DatabaseStore.findAll(Event, accountId: account.id).where([
|
||||
Event.attributes.end.greaterThan(@_range.start),
|
||||
Event.attributes.start.lessThan(@_range.end)
|
||||
]).order(Event.attributes.start.ascending()).then (events) =>
|
||||
@_events = events
|
||||
@trigger(@)
|
||||
|
||||
_refetchFromAPI: ->
|
||||
account = AccountStore.current()
|
||||
return unless account
|
||||
|
||||
# Trigger a request to the API
|
||||
oneDayAgo = Math.round(moment({hour: 0, milliseconds: -1}).valueOf() / 1000.0)
|
||||
DatabaseStore.findAll(Calendar, accountId: account.id).then (calendars) ->
|
||||
calendars.forEach (calendar) ->
|
||||
NylasAPI.getCollection(account.id, 'events', {calendar_id: calendar.id, ends_after: oneDayAgo})
|
||||
|
||||
# Inbound Events
|
||||
|
||||
_onAccountChanged: ->
|
||||
@_refetchFromAPI()
|
||||
@_populate()
|
||||
|
||||
_onDataChanged: (change) ->
|
||||
if change.objectClass == Calendar.name
|
||||
@_refetchFromAPI()
|
||||
if change.objectClass == Event.name
|
||||
@_populate()
|
||||
|
||||
module.exports = CalendarBarEventStore
|
|
@ -1,37 +0,0 @@
|
|||
React = require 'react'
|
||||
{Actions} = require("nylas-exports")
|
||||
moment = require 'moment'
|
||||
|
||||
# TODO: This file is out of date!
|
||||
return
|
||||
|
||||
class CalendarBarItem extends React.Component
|
||||
render: =>
|
||||
style =
|
||||
left: @props.item.xPercent
|
||||
top: @props.item.yPercent
|
||||
width: @props.item.wPercent
|
||||
height: @props.item.hPercent
|
||||
zIndex: @props.item.z
|
||||
<div className="event" style={style} id={@props.item.event.id}>
|
||||
<span className="title">{@props.item.event.title}</span>
|
||||
<span className="time">{@_time()}</span>
|
||||
</div>
|
||||
|
||||
_time: =>
|
||||
w = @props.item.event.when
|
||||
if w.start_time
|
||||
return moment.unix(w.start_time).format('h:mm a')
|
||||
else if w.time
|
||||
return moment.unix(w.time).format('h:mm a')
|
||||
else if w.start_date
|
||||
return moment.unix(w.start_date).format('MMMM Do')
|
||||
else
|
||||
return ""
|
||||
|
||||
_onClick: (event) =>
|
||||
event.preventDefault()
|
||||
Actions.focusMailView(@props.tag)
|
||||
|
||||
|
||||
module.exports = CalendarBarItem
|
|
@ -1,127 +0,0 @@
|
|||
React = require 'react'
|
||||
{Actions} = require("nylas-exports")
|
||||
CalendarBarItem = require("./calendar-bar-item")
|
||||
CalendarBarEventStore = require ("./calendar-bar-event-store")
|
||||
|
||||
class CalendarBarRow
|
||||
constructor: (initialItem = null) ->
|
||||
@items = []
|
||||
@last = 0
|
||||
if initialItem
|
||||
@last = initialItem.event.end
|
||||
@items.push(initialItem)
|
||||
|
||||
canHoldItem: (item) =>
|
||||
item.event.start > @last
|
||||
|
||||
addItem: (item) =>
|
||||
@last = item.event.end
|
||||
@items.push(item)
|
||||
|
||||
class CalendarBarMarker extends React.Component
|
||||
@displayName: "CalendarBarMarker"
|
||||
|
||||
render: =>
|
||||
classname = "marker"
|
||||
classname += " now" if @props.marker.now
|
||||
<div className={classname} style={left: @props.marker.xPercent} id={@props.marker.xPercent}/>
|
||||
|
||||
class CalendarBar extends React.Component
|
||||
@displayName: "CalendarBar"
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = @_getStateFromStores()
|
||||
|
||||
componentDidMount: =>
|
||||
@unsubscribe = CalendarBarEventStore.listen @_onStoreChange
|
||||
|
||||
# It's important that every React class explicitly stops listening to
|
||||
# atom events before it unmounts. Thank you event-kit
|
||||
# This can be fixed via a Reflux mixin
|
||||
componentWillUnmount: =>
|
||||
@unsubscribe() if @unsubscribe
|
||||
|
||||
render: =>
|
||||
markers = @_getMarkers().map (marker) ->
|
||||
<CalendarBarMarker marker={marker}/>
|
||||
|
||||
items = @_getItemsForEvents(@state.events)
|
||||
items = items.map (item) ->
|
||||
<CalendarBarItem item={item}/>
|
||||
|
||||
<div className="calendar-bar-inner">
|
||||
{markers}
|
||||
{items}
|
||||
</div>
|
||||
|
||||
_onStoreChange: =>
|
||||
@setState @_getStateFromStores()
|
||||
|
||||
_getStateFromStores: =>
|
||||
events: CalendarBarEventStore.events()
|
||||
range: CalendarBarEventStore.range()
|
||||
|
||||
_getMarkers: =>
|
||||
range = @state.range
|
||||
now = (new Date).getTime()/1000 - range.start
|
||||
markers = []
|
||||
for hour in [0..24]
|
||||
time = 60*60*hour
|
||||
markers.push
|
||||
xPercent: (time * 100) / (range.end - range.start) + "%"
|
||||
markers.push
|
||||
now: true
|
||||
xPercent: (now * 100) / (range.end - range.start) + "%"
|
||||
markers
|
||||
|
||||
_getItemsForEvents: (events) =>
|
||||
# Create an array of items with additional metadata needed for our view.
|
||||
# We compute the X and width of elements using their durations as a fraction
|
||||
# of the displayed range
|
||||
range = @state.range
|
||||
items = events.map (event) ->
|
||||
{
|
||||
event: event,
|
||||
z: event.start - range.start
|
||||
xPercent: (event.start - range.start) * 100 / (range.end - range.start) + "%",
|
||||
wPercent: (event.end - event.start) * 100 / (range.end - range.start) + "%"
|
||||
}
|
||||
|
||||
# Compute the number of rows we need by assigning events to rows. This works by
|
||||
# creating virtual "row" objects which hold a series of non-overlapping events and
|
||||
# have a "last" timestamp. For each item, we iterate through the rows:
|
||||
#
|
||||
# - If the event fits in more than one row, we delete all but one of the rows.
|
||||
# This ensures that if we have two overlapping events, the next event that
|
||||
# does not overlap goes back to taking all of the available height. (Rows no
|
||||
# longer necessary)
|
||||
#
|
||||
# - If the event does not fit in any rows, we create a new row, and tell all of
|
||||
# the items in existing rows that they're now sharing space with a new row.
|
||||
|
||||
rows = [new CalendarBarRow]
|
||||
for item in items
|
||||
for x in [rows.length-1..0] by -1
|
||||
if rows[x].canHoldItem(item)
|
||||
rows.splice(item.rowIndex, 1) unless item.rowIndex is undefined
|
||||
rows[x].addItem(item)
|
||||
item.rowIndex = x
|
||||
|
||||
if item.rowIndex is undefined
|
||||
rows.push(new CalendarBarRow(item))
|
||||
item.rowIndex = rows.length - 1
|
||||
for row in rows
|
||||
for item in row.items
|
||||
item.rowCount += 1
|
||||
|
||||
item.rowCount = rows.length
|
||||
|
||||
# Now that each item knows what row it's in and how many rows are being displayed
|
||||
# alongside it, we can assign fractional positions to them.
|
||||
for item in items
|
||||
item.yPercent = (item.rowIndex / item.rowCount) * 100 + "%"
|
||||
item.hPercent = (100.0 / item.rowCount) + "%"
|
||||
|
||||
items
|
||||
|
||||
module.exports = CalendarBar
|
|
@ -1,9 +0,0 @@
|
|||
React = require "react"
|
||||
|
||||
module.exports =
|
||||
activate: (@state) ->
|
||||
# Package turned off for now
|
||||
|
||||
deactivate: ->
|
||||
|
||||
serialize: -> @state
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "calendar-bar",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Footer bar that shows your daily availability",
|
||||
"license": "Proprietary",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
@import "ui-variables";
|
||||
@import "ui-mixins";
|
||||
|
||||
|
||||
#calendar-bar {
|
||||
height: 45px;
|
||||
overflow: hidden;
|
||||
order: 50;
|
||||
box-shadow: @standard-shadow-up;
|
||||
z-index:2; // allows shadow over other elements
|
||||
position: relative;
|
||||
|
||||
.marker {
|
||||
position: absolute;
|
||||
border-left:1px solid @border-color-subtle;
|
||||
width:1px;
|
||||
height:100%;
|
||||
top:0;
|
||||
z-index:0;
|
||||
&.now {
|
||||
width:4px;
|
||||
z-index:1000;
|
||||
border-right:1px solid white;
|
||||
border-left:1px solid white;
|
||||
background-color:@background-color-success;
|
||||
}
|
||||
}
|
||||
|
||||
.event {
|
||||
position: absolute;
|
||||
border:2px solid white;
|
||||
background-color: @background-color-accent;
|
||||
padding:3px;
|
||||
|
||||
span {
|
||||
float:left;
|
||||
font-weight: normal;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow:hidden;
|
||||
color: @text-color-inverse;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
span.title {
|
||||
font-size:12px;
|
||||
}
|
||||
span.time {
|
||||
font-size:10px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
1
internal_packages/file-list/.gitignore
vendored
1
internal_packages/file-list/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
node_modules
|
|
@ -1,55 +0,0 @@
|
|||
Reflux = require 'reflux'
|
||||
_ = require 'underscore'
|
||||
fs = require 'fs'
|
||||
|
||||
{WorkspaceStore,
|
||||
FocusedContentStore,
|
||||
FileDownloadStore,
|
||||
Actions} = require 'nylas-exports'
|
||||
|
||||
module.exports =
|
||||
FileFrameStore = Reflux.createStore
|
||||
init: ->
|
||||
@_resetInstanceVars()
|
||||
@_afterViewUpdate = []
|
||||
|
||||
@listenTo FocusedContentStore, @_onFocusedContentChange
|
||||
@listenTo FileDownloadStore, @_onFileDownloadChange
|
||||
|
||||
file: ->
|
||||
@_file
|
||||
|
||||
ready: ->
|
||||
@_ready
|
||||
|
||||
download: ->
|
||||
@_download
|
||||
|
||||
_resetInstanceVars: ->
|
||||
@_file = null
|
||||
@_download = null
|
||||
@_ready = false
|
||||
|
||||
_update: ->
|
||||
|
||||
_onFileDownloadChange: ->
|
||||
@_download = FileDownloadStore.downloadDataForFile(@_file.id) if @_file
|
||||
if @_file and @_ready is false and not @_download
|
||||
@_ready = true
|
||||
@trigger()
|
||||
|
||||
_onFocusedContentChange: (change) ->
|
||||
return unless change.impactsCollection('file')
|
||||
|
||||
@_file = FocusedContentStore.focused('file')
|
||||
if @_file
|
||||
filepath = FileDownloadStore.pathForFile(@_file)
|
||||
fs.exists filepath, (exists) =>
|
||||
Actions.fetchFile(@_file) if not exists
|
||||
@_download = FileDownloadStore.downloadDataForFile(@_file.id)
|
||||
@_ready = not @_download
|
||||
@trigger()
|
||||
else
|
||||
@_ready = false
|
||||
@_download = null
|
||||
@trigger()
|
|
@ -1,39 +0,0 @@
|
|||
React = require 'react'
|
||||
_ = require "underscore"
|
||||
{Utils, FileDownloadStore, Actions} = require 'nylas-exports'
|
||||
{Spinner, EventedIFrame} = require 'nylas-component-kit'
|
||||
FileFrameStore = require './file-frame-store'
|
||||
|
||||
class FileFrame extends React.Component
|
||||
@displayName: 'FileFrame'
|
||||
|
||||
render: =>
|
||||
src = if @state.ready then @state.filepath else ''
|
||||
if @state.file
|
||||
<div className="file-frame-container">
|
||||
<EventedIFrame src={src} />
|
||||
<Spinner visible={!@state.ready} />
|
||||
</div>
|
||||
else
|
||||
<div></div>
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = @getStateFromStores()
|
||||
|
||||
componentDidMount: =>
|
||||
@_unsubscribers = []
|
||||
@_unsubscribers.push FileFrameStore.listen @_onChange
|
||||
|
||||
componentWillUnmount: =>
|
||||
unsubscribe() for unsubscribe in @_unsubscribers
|
||||
|
||||
getStateFromStores: =>
|
||||
file: FileFrameStore.file()
|
||||
filepath: FileDownloadStore.pathForFile(FileFrameStore.file())
|
||||
ready: FileFrameStore.ready()
|
||||
|
||||
_onChange: =>
|
||||
@setState(@getStateFromStores())
|
||||
|
||||
|
||||
module.exports = FileFrame
|
|
@ -1,20 +0,0 @@
|
|||
Reflux = require 'reflux'
|
||||
_ = require 'underscore'
|
||||
{File,
|
||||
DatabaseStore,
|
||||
DatabaseView} = require 'nylas-exports'
|
||||
|
||||
module.exports =
|
||||
FileListStore = Reflux.createStore
|
||||
init: ->
|
||||
@listenTo DatabaseStore, @_onDataChanged
|
||||
|
||||
@_view = new DatabaseView(File, matchers: [File.attributes.filename.not('')])
|
||||
@listenTo @_view, => @trigger({})
|
||||
|
||||
view: ->
|
||||
@_view
|
||||
|
||||
_onDataChanged: (change) ->
|
||||
return unless change.objectClass is File.name
|
||||
@_view.invalidate({shallow: true, changed: change.objects})
|
|
@ -1,51 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
React = require 'react'
|
||||
{ListTabular, MultiselectList} = require 'nylas-component-kit'
|
||||
{Actions,
|
||||
DatabaseStore,
|
||||
ComponentRegistry} = require 'nylas-exports'
|
||||
FileListStore = require './file-list-store'
|
||||
|
||||
class FileList extends React.Component
|
||||
@displayName: 'FileList'
|
||||
|
||||
@containerRequired: false
|
||||
|
||||
componentWillMount: =>
|
||||
prettySize = (size) ->
|
||||
units = ['GB', 'MB', 'KB', 'bytes']
|
||||
while size > 1024
|
||||
size /= 1024
|
||||
units.pop()
|
||||
size = "#{(Math.ceil(size * 10) / 10)}"
|
||||
pretty = units.pop()
|
||||
"#{size} #{pretty}"
|
||||
|
||||
c1 = new ListTabular.Column
|
||||
name: "Name"
|
||||
flex: 1
|
||||
resolver: (file) =>
|
||||
<div>{file.displayName()}</div>
|
||||
|
||||
c2 = new ListTabular.Column
|
||||
name: "Size"
|
||||
width: '100px'
|
||||
resolver: (file) =>
|
||||
<div>{prettySize(file.size)}</div>
|
||||
|
||||
@columns = [c1, c2]
|
||||
|
||||
render: =>
|
||||
<MultiselectList
|
||||
dataStore={FileListStore}
|
||||
columns={@columns}
|
||||
commands={{}}
|
||||
onDoubleClick={@_onDoubleClick}
|
||||
itemPropsProvider={ -> {} }
|
||||
className="file-list"
|
||||
collection="file" />
|
||||
|
||||
_onDoubleClick: (item) =>
|
||||
|
||||
|
||||
module.exports = FileList
|
|
@ -1,14 +0,0 @@
|
|||
React = require "react/addons"
|
||||
FileListStore = require './file-list-store'
|
||||
{MultiselectActionBar} = require 'nylas-component-kit'
|
||||
|
||||
class FileSelectionBar extends React.Component
|
||||
@displayName: 'FileSelectionBar'
|
||||
|
||||
render: =>
|
||||
<MultiselectActionBar
|
||||
dataStore={FileListStore}
|
||||
collection="file" />
|
||||
|
||||
|
||||
module.exports = FileSelectionBar
|
|
@ -1,30 +0,0 @@
|
|||
# FileFrame = require "./file-frame"
|
||||
# FileList = require './file-list'
|
||||
# FileSelectionBar = require './file-selection-bar'
|
||||
# {ComponentRegistry,
|
||||
# WorkspaceStore} = require 'nylas-exports'
|
||||
|
||||
module.exports =
|
||||
|
||||
activate: (@state={}) ->
|
||||
# WorkspaceStore.defineSheet 'Files', {root: true, supportedModes: ['list'], name: 'Files'},
|
||||
# list: ['RootSidebar', 'FileList']
|
||||
#
|
||||
# WorkspaceStore.defineSheet 'File', {supportedModes: ['list']},
|
||||
# list: ['File']
|
||||
#
|
||||
# ComponentRegistry.register FileList,
|
||||
# location: WorkspaceStore.Location.FileList
|
||||
#
|
||||
# ComponentRegistry.register FileSelectionBar,
|
||||
# location: WorkspaceStore.Location.FileList.Toolbar
|
||||
#
|
||||
# ComponentRegistry.register FileFrame,
|
||||
# location: WorkspaceStore.Location.File
|
||||
|
||||
deactivate: ->
|
||||
# ComponentRegistry.unregister(FileList)
|
||||
# ComponentRegistry.unregister(FileSelectionBar)
|
||||
# ComponentRegistry.unregister(FileFrame)
|
||||
# WorkspaceStore.undefineSheet('Files')
|
||||
# WorkspaceStore.undefineSheet('File')
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "file-list",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "View files",
|
||||
"license": "Proprietary",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
@import "ui-variables";
|
||||
@import "ui-mixins";
|
||||
|
||||
@message-max-width: 800px;
|
||||
|
||||
.file-frame-container {
|
||||
width:100%;
|
||||
height:100%;
|
||||
|
||||
iframe {
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:0;
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
{DraftStoreExtension} = require 'nylas-exports'
|
||||
|
||||
class TemplatesDraftStoreExtension extends DraftStoreExtension
|
||||
|
||||
@warningsForSending: (draft) ->
|
||||
warnings = []
|
||||
if draft.body.search(/<code[^>]*empty[^>]*>/i) > 0
|
||||
warnings.push("with an empty template area")
|
||||
warnings
|
||||
|
||||
@finalizeSessionBeforeSending: (session) ->
|
||||
body = session.draft().body
|
||||
clean = body.replace(/<\/?code[^>]*>/g, '')
|
||||
if body != clean
|
||||
session.changes.add(body: clean)
|
||||
|
||||
@onMouseUp: (editableNode, range, event) ->
|
||||
parent = range.startContainer?.parentNode
|
||||
parentCodeNode = null
|
||||
|
||||
while parent and parent isnt editableNode
|
||||
if parent.classList?.contains('var') and parent.tagName is 'CODE'
|
||||
parentCodeNode = parent
|
||||
break
|
||||
parent = parent.parentNode
|
||||
|
||||
isSinglePoint = range.startContainer is range.endContainer and range.startOffset is range.endOffset
|
||||
|
||||
if isSinglePoint and parentCodeNode
|
||||
range.selectNode(parentCodeNode)
|
||||
selection = document.getSelection()
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
|
||||
@onFocusPrevious: (editableNode, range, event) ->
|
||||
@onFocusShift(editableNode, range, event, -1)
|
||||
|
||||
@onFocusNext: (editableNode, range, event) ->
|
||||
@onFocusShift(editableNode, range, event, 1)
|
||||
|
||||
@onFocusShift: (editableNode, range, event, delta) ->
|
||||
return unless range
|
||||
|
||||
# Try to find the node that the selection range is
|
||||
# currently intersecting with (inside, or around)
|
||||
parentCodeNode = null
|
||||
nodes = editableNode.querySelectorAll('code.var')
|
||||
for node in nodes
|
||||
if range.intersectsNode(node)
|
||||
parentCodeNode = node
|
||||
|
||||
|
||||
if parentCodeNode
|
||||
if range.startOffset is range.endOffset and parentCodeNode.classList.contains('empty')
|
||||
# If the current node is empty and it's a single insertion point,
|
||||
# select the current node rather than advancing to the next node
|
||||
selectNode = parentCodeNode
|
||||
else
|
||||
# advance to the next code node
|
||||
matches = editableNode.querySelectorAll('code.var')
|
||||
matchIndex = -1
|
||||
for match, idx in matches
|
||||
if match is parentCodeNode
|
||||
matchIndex = idx
|
||||
break
|
||||
if matchIndex != -1 and matchIndex + delta >= 0 and matchIndex + delta < matches.length
|
||||
selectNode = matches[matchIndex+delta]
|
||||
|
||||
else
|
||||
# We're not currently intersecting a code node. Find the one we want
|
||||
# to move to by scanning for the next one in the DOM. Traversing the
|
||||
# structure of the email would be hard, so instead we look for the next
|
||||
# one that is *visually* to the left or beneath the current one, vice
|
||||
# versa for going back (delta -1 case)
|
||||
rangeRect = range.getClientRects()[0]
|
||||
if rangeRect
|
||||
if delta is 1 # next
|
||||
for node in nodes
|
||||
nodeRect = node.getBoundingClientRect()
|
||||
continue if nodeRect.top < rangeRect.top
|
||||
if nodeRect.top > rangeRect.top or nodeRect.left > rangeRect.left
|
||||
selectNode = node
|
||||
break
|
||||
else if delta is -1 # previous
|
||||
for node in nodes by -1
|
||||
nodeRect = node.getBoundingClientRect()
|
||||
continue if nodeRect.top > rangeRect.top
|
||||
if nodeRect.top < rangeRect.top or nodeRect.left < rangeRect.left
|
||||
selectNode = node
|
||||
break
|
||||
|
||||
if selectNode
|
||||
range.selectNode(selectNode)
|
||||
selection = document.getSelection()
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@onInput: (editableNode, event) ->
|
||||
selection = document.getSelection()
|
||||
|
||||
isWithinNode = (node) ->
|
||||
test = selection.baseNode
|
||||
while test isnt editableNode
|
||||
return true if test is node
|
||||
test = test.parentNode
|
||||
return false
|
||||
|
||||
codeTags = editableNode.querySelectorAll('code.var.empty')
|
||||
for codeTag in codeTags
|
||||
if selection.containsNode(codeTag) or isWithinNode(codeTag)
|
||||
codeTag.classList.remove('empty')
|
||||
|
||||
|
||||
module.exports = TemplatesDraftStoreExtension
|
|
@ -1,25 +0,0 @@
|
|||
React = require "react"
|
||||
{ComponentRegistry, DraftStore} = require 'nylas-exports'
|
||||
TemplatePicker = require './template-picker'
|
||||
TemplateStatusBar = require './template-status-bar'
|
||||
Extension = require './draft-extension'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
item: null # The DOM item the main React component renders into
|
||||
|
||||
activate: (@state={}) ->
|
||||
ComponentRegistry.register TemplatePicker,
|
||||
role: 'Composer:ActionButton'
|
||||
|
||||
ComponentRegistry.register TemplateStatusBar,
|
||||
role: 'Composer:Footer'
|
||||
|
||||
DraftStore.registerExtension(Extension)
|
||||
|
||||
deactivate: ->
|
||||
ComponentRegistry.unregister(TemplatePicker)
|
||||
ComponentRegistry.unregister(TemplateStatusBar)
|
||||
DraftStore.unregisterExtension(Extension)
|
||||
|
||||
serialize: -> @state
|
|
@ -1,87 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
React = require 'react'
|
||||
TemplateStore = require './template-store'
|
||||
{Actions, Message, DatabaseStore} = require 'nylas-exports'
|
||||
{Popover, Menu, RetinaImg} = require 'nylas-component-kit'
|
||||
|
||||
class TemplatePicker extends React.Component
|
||||
@displayName: 'TemplatePicker'
|
||||
|
||||
@containerStyles:
|
||||
order:2
|
||||
|
||||
constructor: (@props) ->
|
||||
@state =
|
||||
searchValue: ""
|
||||
templates: TemplateStore.items()
|
||||
|
||||
componentDidMount: =>
|
||||
@unsubscribe = TemplateStore.listen @_onStoreChange
|
||||
|
||||
componentWillUnmount: =>
|
||||
@unsubscribe() if @unsubscribe
|
||||
|
||||
render: =>
|
||||
button = <button className="btn btn-toolbar narrow">
|
||||
<RetinaImg name="icon-composer-templates.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
|
||||
<RetinaImg name="icon-composer-dropdown.png" mode={RetinaImg.Mode.ContentIsMask}/>
|
||||
</button>
|
||||
|
||||
headerComponents = [
|
||||
<input type="text"
|
||||
tabIndex="1"
|
||||
key="textfield"
|
||||
className="search"
|
||||
value={@state.searchValue}
|
||||
onChange={@_onSearchValueChange}/>
|
||||
]
|
||||
|
||||
footerComponents = [
|
||||
<div className="item" key="new" onMouseDown={@_onNewTemplate}>Save Draft as Template...</div>
|
||||
<div className="item" key="manage" onMouseDown={@_onManageTemplates}>Open Templates Folder...</div>
|
||||
]
|
||||
|
||||
<Popover ref="popover" className="template-picker pull-right" buttonComponent={button}>
|
||||
<Menu ref="menu"
|
||||
headerComponents={headerComponents}
|
||||
footerComponents={footerComponents}
|
||||
items={@state.templates}
|
||||
itemKey={ (item) -> item.id }
|
||||
itemContent={ (item) -> item.name }
|
||||
onSelect={@_onChooseTemplate}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
|
||||
_filteredTemplates: (search) =>
|
||||
search ?= @state.searchValue
|
||||
items = TemplateStore.items()
|
||||
|
||||
return items unless search.length
|
||||
|
||||
_.filter items, (t) ->
|
||||
t.name.toLowerCase().indexOf(search.toLowerCase()) == 0
|
||||
|
||||
_onStoreChange: =>
|
||||
@setState
|
||||
templates: @_filteredTemplates()
|
||||
|
||||
_onSearchValueChange: =>
|
||||
newSearch = event.target.value
|
||||
@setState
|
||||
searchValue: newSearch
|
||||
templates: @_filteredTemplates(newSearch)
|
||||
|
||||
_onChooseTemplate: (template) =>
|
||||
Actions.insertTemplateId({templateId:template.id, draftClientId: @props.draftClientId})
|
||||
@refs.popover.close()
|
||||
|
||||
_onManageTemplates: =>
|
||||
Actions.showTemplates()
|
||||
|
||||
_onNewTemplate: =>
|
||||
Actions.createTemplate({draftClientId: @props.draftClientId})
|
||||
|
||||
|
||||
module.exports = TemplatePicker
|
|
@ -1,47 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
React = require 'react'
|
||||
{Actions, Message, DraftStore} = require 'nylas-exports'
|
||||
|
||||
class TemplateStatusBar extends React.Component
|
||||
@displayName: 'TemplateStatusBar'
|
||||
|
||||
@containerStyles:
|
||||
textAlign:'center'
|
||||
width:530
|
||||
margin:'auto'
|
||||
|
||||
@propTypes:
|
||||
draftClientId: React.PropTypes.string
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = draft: null
|
||||
|
||||
componentDidMount: =>
|
||||
DraftStore.sessionForClientId(@props.draftClientId).then (_proxy) =>
|
||||
return if @_unmounted
|
||||
return unless _proxy.draftClientId is @props.draftClientId
|
||||
@_proxy = _proxy
|
||||
@unsubscribe = @_proxy.listen(@_onDraftChange, @)
|
||||
@_onDraftChange()
|
||||
|
||||
componentWillUnmount: =>
|
||||
@_unmounted = true
|
||||
@unsubscribe() if @unsubscribe
|
||||
|
||||
render: =>
|
||||
if @_draftUsesTemplate()
|
||||
<div className="template-status-bar">
|
||||
Press "tab" to quickly fill in the blanks - highlighting will not be visible to recipients.
|
||||
</div>
|
||||
else
|
||||
<div></div>
|
||||
|
||||
_onDraftChange: =>
|
||||
@setState(draft: @_proxy.draft())
|
||||
|
||||
_draftUsesTemplate: =>
|
||||
return unless @state.draft
|
||||
@state.draft.body.search(/<code[^>]*class="var[^>]*>/i) > 0
|
||||
|
||||
|
||||
module.exports = TemplateStatusBar
|
|
@ -1,104 +0,0 @@
|
|||
Reflux = require 'reflux'
|
||||
_ = require 'underscore'
|
||||
{DatabaseStore, DraftStore, Actions, Message} = require 'nylas-exports'
|
||||
shell = require 'shell'
|
||||
path = require 'path'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
TemplateStore = Reflux.createStore
|
||||
init: ->
|
||||
@_setStoreDefaults()
|
||||
@_registerListeners()
|
||||
|
||||
@_templatesDir = path.join(atom.getConfigDirPath(), 'templates')
|
||||
|
||||
# I know this is a bit of pain but don't do anything that
|
||||
# could possibly slow down app launch
|
||||
fs.exists @_templatesDir, (exists) =>
|
||||
if exists
|
||||
@_populate()
|
||||
fs.watch @_templatesDir, => @_populate()
|
||||
else
|
||||
fs.mkdir @_templatesDir, =>
|
||||
fs.watch @_templatesDir, => @_populate()
|
||||
|
||||
|
||||
########### PUBLIC #####################################################
|
||||
|
||||
items: ->
|
||||
@_items
|
||||
|
||||
templatesDirectory: ->
|
||||
@_templatesDir
|
||||
|
||||
|
||||
########### PRIVATE ####################################################
|
||||
|
||||
_setStoreDefaults: ->
|
||||
@_items = []
|
||||
|
||||
_registerListeners: ->
|
||||
@listenTo Actions.insertTemplateId, @_onInsertTemplateId
|
||||
@listenTo Actions.createTemplate, @_onCreateTemplate
|
||||
@listenTo Actions.showTemplates, @_onShowTemplates
|
||||
|
||||
_populate: ->
|
||||
fs.readdir @_templatesDir, (err, filenames) =>
|
||||
@_items = []
|
||||
for filename in filenames
|
||||
continue if filename[0] is '.'
|
||||
displayname = path.basename(filename, path.extname(filename))
|
||||
@_items.push
|
||||
id: filename,
|
||||
name: displayname,
|
||||
path: path.join(@_templatesDir, filename)
|
||||
@trigger(@)
|
||||
|
||||
_onCreateTemplate: ({draftClientId, name, contents} = {}) ->
|
||||
if draftClientId
|
||||
DraftStore.sessionForClientId(draftClientId).then (session) =>
|
||||
draft = session.draft()
|
||||
name ?= draft.subject
|
||||
contents ?= draft.body
|
||||
if not name or name.length is 0
|
||||
return @_displayError("Give your draft a subject to name your template.")
|
||||
if not contents or contents.length is 0
|
||||
return @_displayError("To create a template you need to fill the body of the current draft.")
|
||||
@_writeTemplate(name, contents)
|
||||
|
||||
else
|
||||
if not name or name.length is 0
|
||||
return @_displayError("You must provide a name for your template.")
|
||||
if not contents or contents.length is 0
|
||||
return @_displayError("You must provide contents for your template.")
|
||||
@_writeTemplate(name, contents)
|
||||
|
||||
_onShowTemplates: ->
|
||||
shell.showItemInFolder(@_items[0]?.path || @_templatesDir)
|
||||
|
||||
_displayError: (message) ->
|
||||
dialog = require('remote').require('dialog')
|
||||
dialog.showErrorBox('Template Creation Error', message)
|
||||
|
||||
_writeTemplate: (name, contents) ->
|
||||
filename = "#{name}.html"
|
||||
templatePath = path.join(@_templatesDir, filename)
|
||||
fs.writeFile templatePath, contents, (err) =>
|
||||
@_displayError(err) if err
|
||||
shell.showItemInFolder(templatePath)
|
||||
@_items.push
|
||||
id: filename,
|
||||
name: name,
|
||||
path: templatePath
|
||||
@trigger(@)
|
||||
|
||||
_onInsertTemplateId: ({templateId, draftClientId} = {}) ->
|
||||
template = _.find @_items, (item) -> item.id is templateId
|
||||
return unless template
|
||||
|
||||
fs.readFile template.path, (err, data) ->
|
||||
body = data.toString()
|
||||
DraftStore.sessionForClientId(draftClientId).then (session) ->
|
||||
session.changes.add(body: body)
|
||||
|
||||
module.exports = TemplateStore
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"name": "message-templates",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Template features galore!",
|
||||
"license": "Proprietary",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"windowTypes": {
|
||||
"default": true,
|
||||
"composer": true
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
{Message, Actions, DatabaseStore, DraftStore} = require 'nylas-exports'
|
||||
TemplateStore = require '../lib/template-store'
|
||||
fs = require 'fs-plus'
|
||||
shell = require 'shell'
|
||||
|
||||
stubTemplatesDir = TemplateStore.templatesDirectory()
|
||||
|
||||
stubTemplateFiles = {
|
||||
'template1.html': '<p>bla1</p>',
|
||||
'template2.html': '<p>bla2</p>'
|
||||
}
|
||||
|
||||
stubTemplates = [
|
||||
{id: 'template1.html', name: 'template1', path: "#{stubTemplatesDir}/template1.html"},
|
||||
{id: 'template2.html', name: 'template2', path: "#{stubTemplatesDir}/template2.html"},
|
||||
]
|
||||
|
||||
describe "TemplateStore", ->
|
||||
beforeEach ->
|
||||
spyOn(fs, 'mkdir')
|
||||
spyOn(shell, 'showItemInFolder').andCallFake ->
|
||||
spyOn(fs, 'writeFile').andCallFake (path, contents, callback) ->
|
||||
callback(null)
|
||||
spyOn(fs, 'readFile').andCallFake (path, callback) ->
|
||||
filename = path.split('/').pop()
|
||||
callback(null, stubTemplateFiles[filename])
|
||||
|
||||
it "should create the templates folder if it does not exist", ->
|
||||
spyOn(fs, 'exists').andCallFake (path, callback) -> callback(false)
|
||||
TemplateStore.init()
|
||||
expect(fs.mkdir).toHaveBeenCalled()
|
||||
|
||||
it "should expose templates in the templates directory", ->
|
||||
spyOn(fs, 'exists').andCallFake (path, callback) -> callback(true)
|
||||
spyOn(fs, 'readdir').andCallFake (path, callback) -> callback(null, Object.keys(stubTemplateFiles))
|
||||
TemplateStore.init()
|
||||
expect(TemplateStore.items()).toEqual(stubTemplates)
|
||||
|
||||
it "should watch the templates directory and reflect changes", ->
|
||||
watchCallback = null
|
||||
watchFired = false
|
||||
|
||||
spyOn(fs, 'exists').andCallFake (path, callback) -> callback(true)
|
||||
spyOn(fs, 'watch').andCallFake (path, callback) -> watchCallback = callback
|
||||
spyOn(fs, 'readdir').andCallFake (path, callback) ->
|
||||
if watchFired
|
||||
callback(null, Object.keys(stubTemplateFiles))
|
||||
else
|
||||
callback(null, [])
|
||||
|
||||
TemplateStore.init()
|
||||
expect(TemplateStore.items()).toEqual([])
|
||||
|
||||
watchFired = true
|
||||
watchCallback()
|
||||
expect(TemplateStore.items()).toEqual(stubTemplates)
|
||||
|
||||
describe "insertTemplateId", ->
|
||||
it "should insert the template with the given id into the draft with the given id", ->
|
||||
|
||||
add = jasmine.createSpy('add')
|
||||
spyOn(DraftStore, 'sessionForClientId').andCallFake ->
|
||||
Promise.resolve(changes: {add})
|
||||
|
||||
runs ->
|
||||
TemplateStore._onInsertTemplateId
|
||||
templateId: 'template1.html',
|
||||
draftClientId: 'localid-draft'
|
||||
waitsFor ->
|
||||
add.calls.length > 0
|
||||
runs ->
|
||||
expect(add).toHaveBeenCalledWith
|
||||
body: stubTemplateFiles['template1.html']
|
||||
|
||||
describe "onCreateTemplate", ->
|
||||
beforeEach ->
|
||||
TemplateStore.init()
|
||||
spyOn(DraftStore, 'sessionForClientId').andCallFake (draftClientId) ->
|
||||
if draftClientId is 'localid-nosubject'
|
||||
d = new Message(subject: '', body: '<p>Body</p>')
|
||||
else
|
||||
d = new Message(subject: 'Subject', body: '<p>Body</p>')
|
||||
session =
|
||||
draft: -> d
|
||||
Promise.resolve(session)
|
||||
|
||||
it "should create a template with the given name and contents", ->
|
||||
TemplateStore._onCreateTemplate({name: '123', contents: 'bla'})
|
||||
item = TemplateStore.items()?[0]
|
||||
expect(item.id).toBe "123.html"
|
||||
expect(item.name).toBe "123"
|
||||
expect(item.path.split("/").pop()).toBe "123.html"
|
||||
|
||||
it "should display an error if no name is provided", ->
|
||||
spyOn(TemplateStore, '_displayError')
|
||||
TemplateStore._onCreateTemplate({contents: 'bla'})
|
||||
expect(TemplateStore._displayError).toHaveBeenCalled()
|
||||
|
||||
it "should display an error if no content is provided", ->
|
||||
spyOn(TemplateStore, '_displayError')
|
||||
TemplateStore._onCreateTemplate({name: 'bla'})
|
||||
expect(TemplateStore._displayError).toHaveBeenCalled()
|
||||
|
||||
it "should save the template file to the templates folder", ->
|
||||
TemplateStore._onCreateTemplate({name: '123', contents: 'bla'})
|
||||
path = "#{stubTemplatesDir}/123.html"
|
||||
expect(fs.writeFile).toHaveBeenCalled()
|
||||
expect(fs.writeFile.mostRecentCall.args[0]).toEqual(path)
|
||||
expect(fs.writeFile.mostRecentCall.args[1]).toEqual('bla')
|
||||
|
||||
it "should open the template so you can see it", ->
|
||||
TemplateStore._onCreateTemplate({name: '123', contents: 'bla'})
|
||||
path = "#{stubTemplatesDir}/123.html"
|
||||
expect(shell.showItemInFolder).toHaveBeenCalled()
|
||||
|
||||
describe "when given a draft id", ->
|
||||
it "should create a template from the name and contents of the given draft", ->
|
||||
spyOn(TemplateStore, 'trigger')
|
||||
spyOn(TemplateStore, '_populate')
|
||||
runs ->
|
||||
TemplateStore._onCreateTemplate({draftClientId: 'localid-b'})
|
||||
waitsFor ->
|
||||
TemplateStore.trigger.callCount > 0
|
||||
runs ->
|
||||
expect(TemplateStore.items().length).toEqual(1)
|
||||
|
||||
it "should display an error if the draft has no subject", ->
|
||||
spyOn(TemplateStore, '_displayError')
|
||||
runs ->
|
||||
TemplateStore._onCreateTemplate({draftClientId: 'localid-nosubject'})
|
||||
waitsFor ->
|
||||
TemplateStore._displayError.callCount > 0
|
||||
runs ->
|
||||
expect(TemplateStore._displayError).toHaveBeenCalled()
|
||||
|
||||
describe "onShowTemplates", ->
|
||||
it "should open the templates folder in the Finder", ->
|
||||
TemplateStore._onShowTemplates()
|
||||
expect(shell.showItemInFolder).toHaveBeenCalled()
|
|
@ -1,38 +0,0 @@
|
|||
@import "ui-variables";
|
||||
@import "ui-mixins";
|
||||
|
||||
@code-bg-color: #fcf4db;
|
||||
|
||||
.template-picker {
|
||||
.menu {
|
||||
.content-container {
|
||||
height:150px;
|
||||
overflow-y:scroll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.template-status-bar {
|
||||
background-color: @code-bg-color;
|
||||
color: darken(@code-bg-color, 70%);
|
||||
border: 1.5px solid darken(@code-bg-color, 10%);
|
||||
border-radius: @border-radius-small;
|
||||
padding-top: @padding-small-vertical @padding-small-horizontal @padding-small-vertical @padding-small-horizontal;
|
||||
font-size: @font-size-small;
|
||||
}
|
||||
|
||||
.compose-body #contenteditable {
|
||||
code.var {
|
||||
font: inherit;
|
||||
padding:0;
|
||||
padding-left:2px;
|
||||
padding-right:2px;
|
||||
border-bottom: 1.5px solid darken(@code-bg-color, 10%);
|
||||
background-color: fade(@code-bg-color, 10%);
|
||||
&.empty {
|
||||
color:darken(@code-bg-color, 70%);
|
||||
border-bottom: 1px solid darken(@code-bg-color, 14%);
|
||||
background-color: @code-bg-color;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 5.9 MiB |
|
@ -1,17 +0,0 @@
|
|||
TodayView = require "./today-view"
|
||||
TodayIcon = require "./today-icon"
|
||||
{ComponentRegistry,
|
||||
WorkspaceStore} = require 'nylas-exports'
|
||||
|
||||
module.exports =
|
||||
|
||||
activate: (@state={}) ->
|
||||
# WorkspaceStore.defineSheet 'Today', {root: true, supportedModes: ['list'], name: 'Today', icon: 'today.png'},
|
||||
# list: ['RootSidebar', 'Today']
|
||||
#
|
||||
# ComponentRegistry.register TodayView,
|
||||
# location: WorkspaceStore.Location.Today
|
||||
|
||||
deactivate: ->
|
||||
# ComponentRegistry.unregister(TodayView)
|
||||
# WorkspaceStore.undefineSheet('Today')
|
|
@ -1,32 +0,0 @@
|
|||
React = require 'react'
|
||||
_ = require "underscore"
|
||||
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
|
||||
|
||||
<div className={classes}>{@state.moment.format('D')}</div>
|
||||
|
||||
_setTimeState: =>
|
||||
timeTillNextSecond = (60 - (new Date).getSeconds()) * 1000
|
||||
@_timer = setTimeout(@_setTimeState, timeTillNextSecond)
|
||||
@setState(moment: moment())
|
||||
|
||||
|
||||
module.exports = TodayIcon
|
|
@ -1,83 +0,0 @@
|
|||
React = require 'react'
|
||||
_ = require "underscore"
|
||||
{Utils, Actions} = require 'nylas-exports'
|
||||
{Spinner, EventedIFrame} = require 'nylas-component-kit'
|
||||
moment = require 'moment'
|
||||
|
||||
class TodayViewDateTime extends React.Component
|
||||
@displayName: 'TodayViewDateTime'
|
||||
|
||||
constructor: (@props) ->
|
||||
@state =
|
||||
moment: moment()
|
||||
|
||||
componentDidMount: =>
|
||||
@_setTimeState()
|
||||
|
||||
componentWillUnmount: =>
|
||||
clearInterval(@_timer)
|
||||
|
||||
render: =>
|
||||
<div className="centered">
|
||||
<div className="time">{@state.moment.format('h:mm')}</div>
|
||||
<div className="date">{@state.moment.format('dddd, MMM Do')}</div>
|
||||
</div>
|
||||
|
||||
_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: =>
|
||||
<div className="box">
|
||||
<h2>{@props.name}</h2>
|
||||
</div>
|
||||
|
||||
class TodayView extends React.Component
|
||||
@displayName: 'TodayView'
|
||||
|
||||
constructor: (@props) ->
|
||||
@state = @_getStateFromStores()
|
||||
|
||||
render: =>
|
||||
<div className="today">
|
||||
<div className="inner">
|
||||
<TodayViewDateTime />
|
||||
<div className="boxes">
|
||||
<TodayViewBox name="Conversations">
|
||||
</TodayViewBox>
|
||||
<TodayViewBox name="Events">
|
||||
</TodayViewBox>
|
||||
<TodayViewBox name="Drafts">
|
||||
</TodayViewBox>
|
||||
</div>
|
||||
<div className="to-the-inbox">
|
||||
Inbox
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
componentDidMount: =>
|
||||
@_unsubscribers = []
|
||||
|
||||
componentWillUnmount: =>
|
||||
unsubscribe() for unsubscribe in @_unsubscribers
|
||||
|
||||
_getStateFromStores: =>
|
||||
{}
|
||||
|
||||
_onChange: =>
|
||||
@setState(@_getStateFromStores())
|
||||
|
||||
|
||||
module.exports = TodayView
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"name": "today",
|
||||
"version": "0.1.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Today View",
|
||||
"license": "Proprietary",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"moment": "^2.8"
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
@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:16px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue