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:
Evan Morikawa 2015-09-10 12:57:40 -04:00
parent 3b342b2e20
commit 626a76622c
39 changed files with 0 additions and 1356 deletions

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -1 +0,0 @@
node_modules

View file

@ -1 +0,0 @@
# React version of thread list

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,9 +0,0 @@
React = require "react"
module.exports =
activate: (@state) ->
# Package turned off for now
deactivate: ->
serialize: -> @state

View file

@ -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": {
}
}

View file

@ -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;
}
}
}

View file

@ -1 +0,0 @@
node_modules

View file

@ -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()

View file

@ -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

View file

@ -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})

View file

@ -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

View file

@ -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

View file

@ -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')

View 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": {
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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}/>
&nbsp;
<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

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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()

View file

@ -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.

Before

Width:  |  Height:  |  Size: 5.9 MiB

View file

@ -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')

View file

@ -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

View file

@ -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

View file

@ -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"
}
}

View file

@ -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;
}
}
}
}