feat(quoted-text): New quoted text engine

Summary:
Add email reply parser

Quoted text detection for HTML

More blockquote specs

Added 'On … wrote:'

Test Plan: edgehill --test

Reviewers: bengotow

Reviewed By: bengotow

Differential Revision: https://phab.nylas.com/D1719
This commit is contained in:
Evan Morikawa 2015-07-08 09:51:33 -07:00
parent c306f553a4
commit e8912e0e2e
61 changed files with 4140 additions and 164 deletions

View file

@ -75,6 +75,10 @@ Exports =
SalesforceObject: require '../src/flux/models/salesforce-object'
SalesforceSchema: require '../src/flux/models/salesforce-schema'
# Services
QuotedPlainTextParser: require '../src/services/quoted-plain-text-parser'
QuotedHTMLParser: require '../src/services/quoted-html-parser'
# Also include all of the model classes
for key, klass of Utils.modelClassMap()
Exports[klass.name] = klass

View file

@ -3,9 +3,10 @@ _ = require 'underscore'
{Utils,
Actions,
UndoManager,
DraftStore,
UndoManager,
FileUploadStore,
QuotedHTMLParser,
FileDownloadStore} = require 'nylas-exports'
{ResizableRegion,
@ -507,14 +508,15 @@ class ComposerView extends React.Component
})
return
body = draft.body.toLowerCase().trim()
body = QuotedHTMLParser.removeQuotedHTML(draft.body.toLowerCase().trim())
forwarded = Utils.isForwardedMessage(draft)
quotedTextIndex = Utils.quotedTextIndex(body)
hasAttachment = (draft.files ? []).length > 0
# Note: In a completely empty reply, quotedTextIndex is 8
# due to opening elements.
bodyIsEmpty = body.length is 0 or 0 <= quotedTextIndex <= 8
# We insert empty br tags before quoted text.
# Our quoted text parser adds additional document elements
onlyHasBr = (/^(<br[^>]*>)+$/gi).test(body)
onlyHasDoc = (/^<head><\/head><body><\/body>$/i).test(body)
bodyIsEmpty = body.length is 0 or onlyHasBr or onlyHasDoc
warnings = []
@ -551,14 +553,8 @@ class ComposerView extends React.Component
Actions.sendDraft(@props.localId)
_mentionsAttachment: (body) =>
body = body.toLowerCase().trim()
attachIndex = body.indexOf("attach")
if attachIndex >= 0
quotedTextIndex = Utils.quotedTextIndex(body)
if quotedTextIndex >= 0
return (attachIndex < quotedTextIndex)
else return true
else return false
body = QuotedHTMLParser.removeQuotedHTML(body.toLowerCase().trim())
return body.indexOf("attach") >= 0
_destroyDraft: =>
Actions.destroyDraft(@props.localId)

View file

@ -2,7 +2,7 @@ _ = require 'underscore'
React = require 'react'
classNames = require 'classnames'
sanitizeHtml = require 'sanitize-html'
{Utils, DraftStore} = require 'nylas-exports'
{Utils, QuotedHTMLParser, DraftStore} = require 'nylas-exports'
FloatingToolbar = require './floating-toolbar'
linkUUID = 0
@ -252,14 +252,16 @@ class ContenteditableComponent extends React.Component
__html: @_applyHTMLDisplayFilters(@props.html)
_applyHTMLDisplayFilters: (html) =>
html = @_removeQuotedTextFromHTML(html) unless @props.mode?.showQuotedText
return html
if @props.mode?.showQuotedText
return html
else
return QuotedHTMLParser.hideQuotedHTML(html)
_unapplyHTMLDisplayFilters: (html) =>
html = @_addQuotedTextToHTML(html) unless @props.mode?.showQuotedText
return html
if @props.mode?.showQuotedText
return html
else
return QuotedHTMLParser.restoreAnnotatedHTML(html)
######### SELECTION MANAGEMENT ##########
@ -1003,21 +1005,7 @@ class ContenteditableComponent extends React.Component
_quotedTextClasses: => classNames
"quoted-text-control": true
"no-quoted-text": @_htmlQuotedTextStart() is -1
"no-quoted-text": not QuotedHTMLParser.hasQuotedHTML(@props.html)
"show-quoted-text": @props.mode?.showQuotedText
_htmlQuotedTextStart: =>
@props.html.search(/(<br\/?>)?(<br\/?>)?<[^>]*gmail_quote/)
_removeQuotedTextFromHTML: (html) =>
quoteStart = @_htmlQuotedTextStart()
if quoteStart is -1 then return html
else return html.substr(0, quoteStart)
_addQuotedTextToHTML: (innerHTML) =>
quoteStart = @_htmlQuotedTextStart()
if quoteStart is -1 then return innerHTML
else return (innerHTML + @props.html.substr(quoteStart))
module.exports = ContenteditableComponent

View file

@ -329,7 +329,7 @@ describe "populated composer", ->
useDraft.call @,
to: [u1]
subject: "Hello World"
body: "<br><br><blockquote class='gmail_quote'>This is my quoted text!</blockquote>"
body: "<head></head><body></body>"
makeComposer.call(@)
@composer._sendDraft()
expect(Actions.sendDraft).not.toHaveBeenCalled()
@ -400,11 +400,11 @@ describe "populated composer", ->
it "warns", -> warn.call(@, "Check out the attached file")
it "warns", -> warn.call(@, "I've added an attachment")
it "warns", -> warn.call(@, "I'm going to attach the file")
it "warns", -> warn.call(@, "Hey attach me <div class='gmail_quote'>sup</div>")
it "warns", -> warn.call(@, "Hey attach me <blockquote class='gmail_quote'>sup</blockquote>")
it "doesn't warn", -> noWarn.call(@, "sup yo")
it "doesn't warn", -> noWarn.call(@, "Look at the file")
it "doesn't warn", -> noWarn.call(@, "Hey there <div class='gmail_quote'>attach</div>")
it "doesn't warn", -> noWarn.call(@, "Hey there <blockquote class='gmail_quote'>attach</blockquote>")
it "doesn't show a warning if you've attached a file", ->
useDraft.call @,

View file

@ -50,7 +50,7 @@ describe "ContenteditableComponent", ->
describe "when showQuotedText is false", ->
it "should only display HTML up to the beginning of the quoted text", ->
@editDiv = ReactTestUtils.findRenderedDOMComponentWithAttr(@componentWithQuote, 'contentEditable')
expect(React.findDOMNode(@editDiv).innerHTML.indexOf('gmail_quote') >= 0).toBe(false)
expect(React.findDOMNode(@editDiv).innerHTML.indexOf('nylas-quoted-text-segment') >= 0).toBe(true)
describe "when showQuotedText is true", ->
beforeEach ->
@ -71,7 +71,7 @@ describe "ContenteditableComponent", ->
describe "when the html is changed", ->
beforeEach ->
@changedHtmlWithoutQuote = 'Changed <strong>NEW 1 HTML</strong>'
@changedHtmlWithoutQuote = '<head></head><body>Changed <strong>NEW 1 HTML</strong><br><br></body>'
@changedHtmlWithQuote = 'Changed <strong>NEW 1 HTML</strong><br><br><blockquote class="gmail_quote">QUOTE</blockquote>'
@performEdit = (newHTML, component = @componentWithQuote) =>
@ -105,10 +105,10 @@ describe "ContenteditableComponent", ->
@componentWithQuote.setState(showQuotedText: false)
@performEdit(@changedHtmlWithoutQuote)
ev = @onChange.mostRecentCall.args[0]
expect(ev.target.value).toEqual(@changedHtmlWithQuote)
expect(ev.target.value).toEqual(@changedHtmlWithoutQuote)
it "should work if the component does not contain quoted text", ->
changed = 'Hallooo! <strong>NEW 1 HTML HTML HTML</strong><br>'
changed = '<head></head><body>Hallooo! <strong>NEW 1 HTML HTML HTML</strong><br></body>'
@component.setState(showQuotedText: true)
@performEdit(changed, @component)
ev = @onChange.mostRecentCall.args[0]

View file

@ -1,7 +1,7 @@
React = require 'react'
_ = require "underscore"
{EventedIFrame} = require 'nylas-component-kit'
{Utils} = require 'nylas-exports'
{QuotedHTMLParser} = require 'nylas-exports'
class EmailFrame extends React.Component
@displayName = 'EmailFrame'
@ -63,7 +63,7 @@ class EmailFrame extends React.Component
if @props.showQuotedText
email
else
Utils.stripQuotedText(email)
QuotedHTMLParser.hideQuotedHTML(email)
module.exports = EmailFrame

View file

@ -1,5 +1,6 @@
remote = require 'remote'
React = require 'react'
{Actions, NamespaceStore} = require 'nylas-exports'
{Actions, NylasAPI, NamespaceStore} = require 'nylas-exports'
{RetinaImg, ButtonDropdown} = require 'nylas-component-kit'
class MessageControls extends React.Component
@ -66,6 +67,62 @@ class MessageControls extends React.Component
return "reply"
else return "reply-all"
_onShowActionsMenu: =>
remote = require('remote')
Menu = remote.require('menu')
MenuItem = remote.require('menu-item')
# Todo: refactor this so that message actions are provided
# dynamically. Waiting to see if this will be used often.
menu = new Menu()
menu.append(new MenuItem({ label: 'Report Issue: Quoted Text', click: => @_onReport('Quoted Text')}))
menu.append(new MenuItem({ label: 'Report Issue: Rendering', click: => @_onReport('Rendering')}))
menu.append(new MenuItem({ type: 'separator'}))
menu.append(new MenuItem({ label: 'Show Original', click: => @_onShowOriginal()}))
menu.popup(remote.getCurrentWindow())
_onReport: (issueType) =>
{Contact, Message, DatabaseStore, NamespaceStore} = require 'nylas-exports'
draft = new Message
from: [NamespaceStore.current().me()]
to: [new Contact(name: "Nylas Team", email: "feedback@nylas.com")]
date: (new Date)
draft: true
subject: "Feedback - Message Display Issue (#{issueType})"
namespaceId: NamespaceStore.current().id
body: @props.message.body
DatabaseStore.persistModel(draft).then =>
DatabaseStore.localIdForModel(draft).then (localId) =>
Actions.sendDraft(localId)
dialog = remote.require('dialog')
dialog.showMessageBox remote.getCurrentWindow(), {
type: 'warning'
buttons: ['OK'],
message: "Thank you."
detail: "The contents of this message have been sent to the Edgehill team and we added to a test suite."
}
_onShowOriginal: =>
fs = require 'fs'
path = require 'path'
BrowserWindow = remote.require('browser-window')
app = remote.require('app')
tmpfile = path.join(app.getPath('temp'), @props.message.id)
NylasAPI.makeRequest
headers:
Accept: 'message/rfc822'
path: "/n/#{@props.message.namespaceId}/messages/#{@props.message.id}"
json:false
success: (body) =>
fs.writeFile tmpfile, body, =>
window = new BrowserWindow(width: 800, height: 600, title: "#{@props.message.subject} - RFC822")
window.loadUrl('file://'+tmpfile)
module.exports = MessageControls
# <InjectedComponentSet className="message-actions"

View file

@ -7,15 +7,14 @@ MessageTimestamp = require "./message-timestamp"
MessageControls = require './message-controls'
{Utils,
Actions,
NylasAPI,
MessageUtils,
QuotedHTMLParser,
ComponentRegistry,
FileDownloadStore} = require 'nylas-exports'
{RetinaImg,
InjectedComponentSet,
InjectedComponent} = require 'nylas-component-kit'
Autolinker = require 'autolinker'
remote = require 'remote'
TransparentPixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNikAQAACIAHF/uBd8AAAAASUVORK5CYII="
MessageBodyWidth = 740
@ -145,64 +144,9 @@ class MessageItem extends React.Component
_quotedTextClasses: => classNames
"quoted-text-control": true
'no-quoted-text': (Utils.quotedTextIndex(@props.message.body) is -1)
'no-quoted-text': not QuotedHTMLParser.hasQuotedHTML(@props.message.body)
'show-quoted-text': @state.showQuotedText
_onReport: (issueType) =>
{Contact, Message, DatabaseStore, NamespaceStore} = require 'nylas-exports'
draft = new Message
from: [NamespaceStore.current().me()]
to: [new Contact(name: "Nylas Team", email: "feedback@nylas.com")]
date: (new Date)
draft: true
subject: "Feedback - Message Display Issue (#{issueType})"
namespaceId: NamespaceStore.current().id
body: @props.message.body
DatabaseStore.persistModel(draft).then =>
DatabaseStore.localIdForModel(draft).then (localId) =>
Actions.sendDraft(localId)
dialog = remote.require('dialog')
dialog.showMessageBox remote.getCurrentWindow(), {
type: 'warning'
buttons: ['OK'],
message: "Thank you."
detail: "The contents of this message have been sent to the Edgehill team and we added to a test suite."
}
_onShowOriginal: =>
fs = require 'fs'
path = require 'path'
BrowserWindow = remote.require('browser-window')
app = remote.require('app')
tmpfile = path.join(app.getPath('temp'), @props.message.id)
NylasAPI.makeRequest
headers:
Accept: 'message/rfc822'
path: "/n/#{@props.message.namespaceId}/messages/#{@props.message.id}"
json:false
success: (body) =>
fs.writeFile tmpfile, body, =>
window = new BrowserWindow(width: 800, height: 600, title: "#{@props.message.subject} - RFC822")
window.loadUrl('file://'+tmpfile)
_onShowActionsMenu: =>
remote = require('remote')
Menu = remote.require('menu')
MenuItem = remote.require('menu-item')
# Todo: refactor this so that message actions are provided
# dynamically. Waiting to see if this will be used often.
menu = new Menu()
menu.append(new MenuItem({ label: 'Report Issue: Quoted Text', click: => @_onReport('Quoted Text')}))
menu.append(new MenuItem({ label: 'Report Issue: Rendering', click: => @_onReport('Rendering')}))
menu.append(new MenuItem({ type: 'separator'}))
menu.append(new MenuItem({ label: 'Show Original', click: => @_onShowOriginal()}))
menu.popup(remote.getCurrentWindow())
_renderHeaderExpansionControl: =>
if @state.detailedHeaders
<div className="header-toggle-control"

View file

@ -7,6 +7,7 @@ ReactTestUtils = React.addons.TestUtils
File,
Thread,
Utils,
QuotedHTMLParser,
FileDownloadStore} = require "nylas-exports"
EmailFrameStub = React.createClass({render: -> <div></div>})
@ -253,7 +254,7 @@ describe "MessageItem", ->
expect(React.findDOMNode(toggle).className.indexOf('show-quoted-text')).toBe(-1)
it "should have the `no quoted text` class if there is no quoted text in the message", ->
spyOn(Utils, 'quotedTextIndex').andCallFake -> -1
spyOn(QuotedHTMLParser, 'hasQuotedHTML').andReturn false
@createComponent()
toggle = ReactTestUtils.findRenderedDOMComponentWithClass(@component, 'quoted-text-control')

View file

@ -0,0 +1,4 @@
this is an email with a correct -- signature.
--
rick

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
<html> <head> <meta http-equiv=\"Content-Type\" content=\"text/html;
charset=us-ascii\"> </head> <body> I saw it this weekend and will totally go
again. :) One of the best movies I've seen recently!&nbsp;<br> <br> <blockquote
class=\"gmail_quote\" style=\"margin:0 0 0 .8ex;border-left:1px #ccc
solid;padding-left:1ex;\"> On Jul 6 2015, at 3:58 pm, Makala Keys
&lt;makala@nylas.com&gt; wrote: <br> Hey Team, <div> <div><br> </div>
<div>Let's go see a movie! The latest Pixar movie, Inside Out, is supposed to
be excellent (link to the trailor below).&nbsp;</div> <div><span
style=\"font-size: 15.9px; line-height: 1.4; background-color: inherit;\">I'm
taking a headcount for the AMC Van Ness 14 show on <b>WED @ 7:45pm. </b>&#43;1s
are welcome and tickets will be covered by the company. Please let me know by
Wednesday at noon, so I can purchase tickets in advance!</span></div>
<div><span style=\"font-size: 15.9px; line-height: 1.4; background-color:
inherit;\"><br> </span></div> <div><span style=\"font-size: 15.9px;
line-height: 1.4; background-color: inherit;\">Thanks,&nbsp;<br>
Makala&nbsp;</span></div> <div><br> </div> <div><span style=\"font-size:
15.9px; line-height: 1.4; background-color: inherit;\"><br> </span></div>
<div><span style=\"font-size: 15.9px; line-height: 1.4; background-color:
inherit;\">https://www.youtube.com/watch?v=seMwpP0yeu4</span><br> </div> <div>
<div> <div><br> </div>
<div>http://www.rottentomatoes.com/m/inside_out_2015/<br> </div> <div><br>
</div> </div> </div> </div> </blockquote> </body> </html>

View file

@ -0,0 +1,3 @@
<head> <meta http-equiv="\&quot;Content-Type\&quot;" content="\&quot;text/html;" charset="us-ascii\&quot;"> </head> <body> I saw it this weekend and will totally go
again. :) One of the best movies I've seen recently!&nbsp;<br> <br>
</body>

View file

@ -0,0 +1 @@
<html> <body> <p>Hi folks</p> <p> What is the best way to clear a Riak bucket of all key, values after running a test?<br/>I am currently using the Java HTTP API. </p> <p> -Abhishek Kona </p> <br/> <p> _______________________________________________ riak-users mailing list riak-users@lists.basho.com http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com </p> </body> </html>

View file

@ -0,0 +1,2 @@
<head></head><body> <p>Hi folks</p> <p> What is the best way to clear a Riak bucket of all key, values after running a test?<br>I am currently using the Java HTTP API. </p> <p> -Abhishek Kona </p> <br> <p> _______________________________________________ riak-users mailing list riak-users@lists.basho.com http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com </p>
</body>

View file

@ -0,0 +1,70 @@
<html><body>
<div>
Hi,<br/>
<blockquote class="foo">
On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
<div>
> Hi folks <br/>
> <br/>
> What is the best way to clear a Riak bucket of all key, values after
<br/>
> running a test? <br/>
> I am currently using the Java HTTP API. <br/>
</div>
</blockquote>
<p>
You can list the keys for the bucket and call delete for each. Or if you
put the keys (and kept track of them in your test) you can delete them
one at a time (without incurring the cost of calling list first.)
</p>
<p>
Something like:
<br/>
<pre>
String bucket = "my_bucket";
BucketResponse bucketResponse = riakClient.listBucket(bucket);
RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
for(String key : bucketInfo.getKeys()) {
riakClient.delete(bucket, key);
}
</pre>
</p>
<p>
would do it.
<br/>
See also
<br/>
http://wiki.basho.com/REST-API.html#Bucket-operations
<br/>
which says
<br/>
"At the moment there is no straightforward way to delete an entire
Bucket. There is, however, an open ticket for the feature. To delete all
the keys in a bucket, youll need to delete them all individually."
</p>
<blockquote>
<div>
> <br/>
> -Abhishek Kona <br/>
> <br/>
> <br/>
> _______________________________________________ <br/>
> riak-users mailing list <br/>
> riak-users@lists.basho.com <br/>
> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com <br/>
</div>
</blockquote>
<br/>
<br/>
<br/>
<br/>
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
</body></html>

View file

@ -0,0 +1,49 @@
<head></head><body>
<div>
Hi,<br>
<p>
You can list the keys for the bucket and call delete for each. Or if you
put the keys (and kept track of them in your test) you can delete them
one at a time (without incurring the cost of calling list first.)
</p>
<p>
Something like:
<br>
</p><pre>
String bucket = "my_bucket";
BucketResponse bucketResponse = riakClient.listBucket(bucket);
RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
for(String key : bucketInfo.getKeys()) {
riakClient.delete(bucket, key);
}
</pre>
<p></p>
<p>
would do it.
<br>
See also
<br>
http://wiki.basho.com/REST-API.html#Bucket-operations
<br>
which says
<br>
"At the moment there is no straightforward way to delete an entire
Bucket. There is, however, an open ticket for the feature. To delete all
the keys in a bucket, youll need to delete them all individually."
</p>
<br>
<br>
<br>
<br>
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
</div></body>

View file

@ -0,0 +1,56 @@
<html><body>
<div>
Oh thanks. <br/>
Having the function would be great. <br/>
-Abhishek Kona <br/>
</div>
<blockquote>
On 01/03/11 7:07 PM, Russell Brown wrote:
> Hi,
> On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
>> Hi folks
>>
>> What is the best way to clear a Riak bucket of all key, values after
>> running a test?
>> I am currently using the Java HTTP API.
> You can list the keys for the bucket and call delete for each. Or if you
> put the keys (and kept track of them in your test) you can delete them
> one at a time (without incurring the cost of calling list first.)
>
> Something like:
>
> String bucket = "my_bucket";
> BucketResponse bucketResponse = riakClient.listBucket(bucket);
> RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
>
> for(String key : bucketInfo.getKeys()) {
> riakClient.delete(bucket, key);
> }
>
>
> would do it.
>
> See also
>
> http://wiki.basho.com/REST-API.html#Bucket-operations
>
> which says
>
> "At the moment there is no straightforward way to delete an entire
> Bucket. There is, however, an open ticket for the feature. To delete all
> the keys in a bucket, youll need to delete them all individually."
>
>> -Abhishek Kona
>>
>>
>> _______________________________________________
>> riak-users mailing list
>> riak-users@lists.basho.com
>> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
>
</blockquote>
</body></html>

View file

@ -0,0 +1,12 @@
<head></head><body>
<div>
Oh thanks. <br>
Having the function would be great. <br>
-Abhishek Kona <br>
</div>
</body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,102 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
Test 123<br/><br><br >
<blockquote ><blockquote>Quote level 2</blockquote></blockquote>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
On Jul 6 2015, at 12:34 pm, spang@nylas.com &lt;spang@nylas.com&gt; wrote: <br>
Karim, Michael and I just discussed this. I'll send you a redwood docs diff later today to speed this along. Here's the summary for now.
<div><br>
</div>
<div>We agreed on:<br>
- add a param to the create/update APIs for events instead of creating a new API<br>
- no body customization for now<br>
- sending notifications defaults to `false`<br>
</div>
<div><br>
</div>
<div>Differences from what we discussed:</div>
<div>- call the parameter `notify_participants` instead of `send_notifications` (it's more explicit about who is getting notified)</div>
<div>- only fail if <i>event creation fails,</i>&nbsp;not if the notifications fail to send*</div>
<div>- to make failure happen less often, we should validate event participants' email addresses and reject requests with bad email addresses as invalid</div>
<div>- on Google, use the `sendNotifications` parameter in the calendar API instead of sending out event notifications ourself, for consistency with expectations and increased reliability</div>
<div><br>
</div>
<div>* We can document that we make a best-effort to deliver notifications, but they may not always succeed. This is a tiny edge case and it shouldn't come up very often, so we shouldn't cause clients to complicate their logic because of it.</div>
<div><br>
</div>
<div>Please include the code for both create and update in your updated diff. It doesn't make sense to ship create only without update, and most of the code is there already.</div>
<div><br>
</div>
<div>Samples:</div>
<div><br>
</div>
<div>POST /n/&lt;ns-id&gt;/events?notify_participants=true -d '{ ... }'</div>
<div>PUT /n/&lt;ns-id&gt;/events?notify_participants=true -d '{ ... }'</div>
<div><br>
</div>
<div>Let's ship this and see what Lever thinks.<br>
</div>
<div><br>
</div>
<br>
<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
On Jul 6 2015, at 9:55 am, Karim Hamidou &lt;karim@nylas.com&gt; wrote: <br>
<div>Christine,</div>
<div><br>
</div>
<div>Here are my notes about the quick chat we had.</div>
<div><br>
</div>
<div>1. We need an invite sending API. We could either:</div>
<div>- add a parameter to the event creation/update API to send emails to the participants</div>
<div>- or have a separate invite sending endpoint.</div>
<div><br>
</div>
<div>We chose to go with the former, because it's simpler.</div>
<div><br>
</div>
<div>2. <b>How would this work?</b>&nbsp;When creating/updating/deleting an event, an API user can set the `send_notifications` parameter to `true`. In this case, the API will generate an event invite email and will send it to the participants.&nbsp;<br>
</div>
<div><br>
</div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">Example: curl -X POST http://localhost:5555/n/namespace_id/events?send_notifications=true &nbsp;&quot;{ title: 'test event', start: ... }&quot;</span><br>
</div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;"><br>
</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">3.
<b>Error handling. &nbsp;</b>Invite sending can fail in a variety of ways because we're sending emails. Because API users need to know if an message went through or not, the API behaves a bit like the synchronous sending API.&nbsp;</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;"><br>
</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">Here's what happens when a user creates an event with send_notifications=true:</span><br>
</div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">1. We create the event in the db</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">2. We try sending emails</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;">3. If it failed, we delete the event from the db and return the SMTP error.</span></div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;"><br>
</span></div>
<div>Of course API users can try re-sending the same invite as often as they wish.</div>
<div><br>
</div>
<div>4. <b>Limitations:</b></div>
<div><b>- </b>it's not possible to define a custom body (though we could have define an ad-hoc `invite_body` field in the event JSON that could be used only for invite sending)</div>
<div>- no support for attached files&nbsp;</div>
<div><br>
</div>
<div>Did I forget anything?</div>
<div><br>
</div>
<div>k</div>
<div><span style="font-size: 15.9px; line-height: 1.4; background-color: inherit;"><b><br>
</b></span></div>
<div><br>
</div>
</blockquote>
</blockquote>
</body>
</html>

View file

@ -0,0 +1,12 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
Test 123<br><br><br>
</body>

View file

@ -0,0 +1,13 @@
Hi folks
What is the best way to clear a Riak bucket of all key, values after
running a test?
I am currently using the Java HTTP API.
-Abhishek Kona
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

View file

@ -0,0 +1,51 @@
Hi,
On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
> Hi folks
>
> What is the best way to clear a Riak bucket of all key, values after
> running a test?
> I am currently using the Java HTTP API.
You can list the keys for the bucket and call delete for each. Or if you
put the keys (and kept track of them in your test) you can delete them
one at a time (without incurring the cost of calling list first.)
Something like:
String bucket = "my_bucket";
BucketResponse bucketResponse = riakClient.listBucket(bucket);
RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
for(String key : bucketInfo.getKeys()) {
riakClient.delete(bucket, key);
}
would do it.
See also
http://wiki.basho.com/REST-API.html#Bucket-operations
which says
"At the moment there is no straightforward way to delete an entire
Bucket. There is, however, an open ticket for the feature. To delete all
the keys in a bucket, youll need to delete them all individually."
>
> -Abhishek Kona
>
>
> _______________________________________________
> riak-users mailing list
> riak-users@lists.basho.com
> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

View file

@ -0,0 +1,55 @@
Oh thanks.
Having the function would be great.
-Abhishek Kona
On 01/03/11 7:07 PM, Russell Brown wrote:
> Hi,
> On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
>> Hi folks
>>
>> What is the best way to clear a Riak bucket of all key, values after
>> running a test?
>> I am currently using the Java HTTP API.
> You can list the keys for the bucket and call delete for each. Or if you
> put the keys (and kept track of them in your test) you can delete them
> one at a time (without incurring the cost of calling list first.)
>
> Something like:
>
> String bucket = "my_bucket";
> BucketResponse bucketResponse = riakClient.listBucket(bucket);
> RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
>
> for(String key : bucketInfo.getKeys()) {
> riakClient.delete(bucket, key);
> }
>
>
> would do it.
>
> See also
>
> http://wiki.basho.com/REST-API.html#Bucket-operations
>
> which says
>
> "At the moment there is no straightforward way to delete an entire
> Bucket. There is, however, an open ticket for the feature. To delete all
> the keys in a bucket, youll need to delete them all individually."
>
>> -Abhishek Kona
>>
>>
>> _______________________________________________
>> riak-users mailing list
>> riak-users@lists.basho.com
>> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
>
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

View file

@ -0,0 +1,5 @@
Awesome! I haven't had another problem with it.
On Aug 22, 2011, at 7:37 PM, defunkt<reply@reply.github.com> wrote:
> Loader seems to be working well.

View file

@ -0,0 +1,15 @@
One: Here's what I've got.
- This would be the first bullet point that wraps to the second line
to the next
- This is the second bullet point and it doesn't wrap
- This is the third bullet point and I'm having trouble coming up with enough
to say
- This is the fourth bullet point
Two:
- Here is another bullet point
- And another one
This is a paragraph that talks about a bunch of stuff. It goes on and on
for a while.

View file

@ -0,0 +1,15 @@
I get proper rendering as well.
Sent from a magnificent torch of pixels
On Dec 16, 2011, at 12:47 PM, Corey Donohoe
<reply@reply.github.com>
wrote:
> Was this caching related or fixed already? I get proper rendering here.
>
> ![](https://img.skitch.com/20111216-m9munqjsy112yqap5cjee5wr6c.jpg)
>
> ---
> Reply to this email directly or view it on GitHub:
> https://github.com/github/github/issues/2278#issuecomment-3182418

View file

@ -0,0 +1,55 @@
Oh thanks.
On the topic of having text, this should show too.
-Abhishek Kona
On 01/03/11 7:07 PM, Russell Brown wrote:
> Hi,
> On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
>> Hi folks
>>
>> What is the best way to clear a Riak bucket of all key, values after
>> running a test?
>> I am currently using the Java HTTP API.
> You can list the keys for the bucket and call delete for each. Or if you
> put the keys (and kept track of them in your test) you can delete them
> one at a time (without incurring the cost of calling list first.)
>
> Something like:
>
> String bucket = "my_bucket";
> BucketResponse bucketResponse = riakClient.listBucket(bucket);
> RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
>
> for(String key : bucketInfo.getKeys()) {
> riakClient.delete(bucket, key);
> }
>
>
> would do it.
>
> See also
>
> http://wiki.basho.com/REST-API.html#Bucket-operations
>
> which says
>
> "At the moment there is no straightforward way to delete an entire
> Bucket. There is, however, an open ticket for the feature. To delete all
> the keys in a bucket, youll need to delete them all individually."
>
>> -Abhishek Kona
>>
>>
>> _______________________________________________
>> riak-users mailing list
>> riak-users@lists.basho.com
>> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
>
_______________________________________________
riak-users mailing list
riak-users@lists.basho.com
http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

View file

@ -0,0 +1,37 @@
On Tue, Apr 29, 2014 at 4:22 PM, Example Dev <sugar@example.com>wrote:
> okay. Well, here's some stuff I can write.
>
> And if I write a 2 second line you and maybe reply under this?
>
> Or if you didn't really feel like it, you could reply under this line.Or
> if you didn't really feel like it, you could reply under this line. Or if
> you didn't really feel like it, you could reply under this line. Or if you
> didn't really feel like it, you could reply under this line.
>
I will reply under this one
>
> okay?
>
and under this.
>
> -- Tim
>
> On Tue, April 29, 2014 at 4:21 PM, Tim Haines <tmhaines@example.com> wrote:
> > hi there
> >
> > After you reply to this I'm going to send you some inline responses.
> >
> > --
> > Hey there, this is my signature
>
>
>
--
Hey there, this is my signature

View file

@ -0,0 +1,23 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;">
<div><span class="message_content">
<pre class="special_formatting"><font face="Calibri"><span style="font-size: 15px;">Hi Jeff,
Quick update on the event bugs:
- I fixed the bug where events would be incorrectly marked as read-only.
- We expose RRULEs as valid JSON now.
We're currently testing the fixes, they should ship early next week.
Concerning timezones, an event should always be associated with a timezone. Having a NULL value instead is a bug on our end. I will be working on fixing this on Monday and will let you know when it's fixed.
Thanks for your detailed bug reports,</span></font></pre>
<pre class="special_formatting"><font face="Calibri"><span style="font-size: 15px;">
Karim</span></font></pre>
<pre class="special_formatting" style="color: rgb(0, 0, 0); font-family: Calibri, sans-serif; font-size: 14px;"><span style="font-family: Calibri; font-size: 11pt; font-weight: bold;">From: </span><span style="font-family: Calibri; font-size: 11pt;"> Kavya Joshi &lt;</span><a href="mailto:kavya@nylas.com" style="font-family: Calibri; font-size: 11pt;">kavya@nylas.com</a><span style="font-family: Calibri; font-size: 11pt;">&gt;</span></pre>
</span></div>
</body>

View file

@ -0,0 +1,148 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>It's be great to talk with Jonathan-- feel free to connect us. Thanks.<br>
</p>
<p><br>
</p>
<p>The bug I mentioned manifested itself like this:<br>
</p>
<p><br>
</p>
<div class="remarkup-code-block" data-code-lang="text" data-sigil="remarkup-code-block" style="margin: 0px 0px 12px; padding: 0px; border: 0px; white-space: pre; font-family: 'Segoe UI', 'Segoe UI Web Regular', 'Segoe UI Symbol', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; line-height: 18.8500003814697px; background-color: rgb(255, 255, 255);">
<pre class="remarkup-code" style="margin-top: 0px; margin-bottom: 0px; padding: 8px; border: 1px solid rgb(233, 219, 205); overflow: auto; font-family: Menlo, Consolas, Monaco, monospace; font-stretch: normal; font-size: 10px; line-height: normal; background: rgb(253, 243, 218);">Traceback (most recent call last):
File &quot;/usr/local/lib/python2.7/dist-packages/gevent/greenlet.py&quot;, line 327, in run
result = self._run(*self.args, **self.kwargs)
File &quot;/vagrant/inbox/mailsync/backends/imap/generic.py&quot;, line 190, in _run
fail_classes=self.retry_fail_classes)
File &quot;/vagrant/inbox/util/concurrency.py&quot;, line 120, in retry_and_report_killed
**reset_params)()
File &quot;/vagrant/inbox/util/concurrency.py&quot;, line 73, in wrapped
return func(*args, **kwargs)
File &quot;/vagrant/inbox/mailsync/backends/imap/generic.py&quot;, line 217, in _run_impl
self.state = self.state_handlers[old_state]()
File &quot;/vagrant/inbox/util/concurrency.py&quot;, line 73, in wrapped
return func(*args, **kwargs)
File &quot;/vagrant/inbox/mailsync/backends/imap/generic.py&quot;, line 270, in initial_sync
self.initial_sync_impl(crispin_client)
File &quot;/vagrant/inbox/mailsync/backends/imap/generic.py&quot;, line 293, in initial_sync_impl
remote_uids = crispin_client.all_uids()
File &quot;/vagrant/inbox/crispin.py&quot;, line 489, in all_uids
fetch_result = self.conn.search(['ALL', 'UID'])
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py&quot;, line 588, in search
return self._search(normalise_search_criteria(criteria), charset)
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py&quot;, line 621, in _search
for item in parse_response(data):
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py&quot;, line 46, in parse_response
return tuple(gen_parsed_response(data))
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py&quot;, line 56, in gen_parsed_response
for token in src:
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py&quot;, line 118, in __iter__
for tok in self.read_token_stream(iter(source)):
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py&quot;, line 149, in __iter__
return PushableIterator(six.iterbytes(self.src_text))
File &quot;/usr/local/lib/python2.7/dist-packages/imapclient/six.py&quot;, line 597, in iterbytes
return (ord(byte) for byte in buf)
TypeError: 'NoneType' object is not iterable
&lt;FolderSyncEngine at 0x5e4e550&gt; failed with TypeError</pre>
<div><br>
</div>
</div>
<p><br>
</p>
<p>But turns out Tom fixed it <a href="https://bitbucket.org/mjs0/imapclient/commits/1de7a0b63367d49587b0454804d4e29079f84f0b?at=default">
here</a>.&nbsp;&nbsp;I don't think it's yet on PyPI.<br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
<div style="color: rgb(0, 0, 0);">
<hr tabindex="-1" style="display:inline-block; width:98%">
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif" color="#000000" style="font-size:11pt"><b>From:</b> Menno Smits &lt;menno@freshfoo.com&gt;<br>
<b>Sent:</b> Wednesday, May 27, 2015 2:41 AM<br>
<b>To:</b> Michael Grinich<br>
<b>Cc:</b> Christine Spang<br>
<b>Subject:</b> Re: Thoughts</font>
<div>&nbsp;</div>
</div>
<div>
<div>Hi Michael,<br>
</div>
<div>&nbsp;</div>
<div>No problems about the delay. I know what it's like.<br>
</div>
<div>&nbsp;</div>
<div>Another interesting development: after our call I put out my feelers to see if any of the developers that I know and trust would be interested in tackling this work, and I got a bite from a former colleague and good friend, Jonathan Hartley (<a href="http://tartley.com/">http://tartley.com/</a>).
He's one of the smartest developers I know - with huge amounts of experience with Python - and is highly disciplined about testing and code quality. On top of that, he's in the process of arranging a move to the US (his wife his American) so that could work
well too. He has no experience with IMAP or Go, but these are things I'm confident he could quickly pick up. Due to the pending move to the US, he currently on a short contract so could be available on fairly short notice.<br>
</div>
<div>&nbsp;</div>
<div>Are you interested in talking to him? He could give you a full 5 days a week working on IMAPClient and related bits.<br>
</div>
<div>&nbsp;</div>
<div>- Menno<br>
</div>
<div>&nbsp;</div>
<div>p.s. Can you give me details on the bug that you found today?<br>
</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>On Wed, 27 May 2015, at 06:59, Michael Grinich wrote:<br>
</div>
<blockquote type="cite">
<div>Hi Menno,<br>
</div>
<div>&nbsp;</div>
<div>Sorry for the delay.&nbsp;<br>
</div>
<div>&nbsp;</div>
<div>Having you work dedicated 1 day a week for Nylas would be fantastic. We already have several low-hanging IMAPclient projects we need help with, and there are many places in our sync engine codebase that I think you could make huge contributions to.<br>
</div>
<div>&nbsp;</div>
<div>So in short, yes. We'd be ready to get started immediately. (Already had a bug come up today where IMAPclient fails when folders have no items in them...)<br>
</div>
<div>&nbsp;</div>
<div>--Michael<br>
</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<blockquote style="margin-top:0px; margin-right:0px; margin-bottom:0px; margin-left:0.8ex; border-left-width:1px; border-left-style:solid; border-left-color:rgb(204,204,204); padding-left:1ex">
<div>On May 21 2015, at 7:44 pm, Menno Smits &lt;menno@freshfoo.com&gt; wrote: <br>
</div>
<p>Hi Michael,<br>
</p>
<p>It was great to talk to you and Christine earlier this week.<br>
</p>
<p>I've been thinking about ways that we could make this work. I'm really<br>
not ready to leave my current position at Canonical but I'd be prepared<br>
to consider dropping my hours to work for Nylas part-time. Would you<br>
consider having me work for Nylas one full day a week if I could<br>
negotiate my hours down to 4 days a week at Canonical? I think they<br>
might be open to that (any less could be a struggle).</p>
<p>I realise this is probably less of a commitment from me than you'd<br>
probably like but one day a week would give me much more time to work on<br>
IMAPClient than I have now. You'll get you the features you need much<br>
sooner. If this seems workable to you I can start the conversation with<br>
my managers.</p>
<p>I'm also writing to a couple top notch developers that I trust about the<br>
possibility of working with you on this (they're both in London). I<br>
believe that one in particular could be thinking about leaving his<br>
current role.</p>
<p>Cheers,<br>
Menno</p>
</blockquote>
</blockquote>
<div>&nbsp;</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,25 @@
Outlook with a reply
------------------------------
*From:* Google Apps Sync Team [mailto:mail-noreply@google.com]
*Sent:* Thursday, February 09, 2012 1:36 PM
*To:* jow@xxxx.com
*Subject:* Google Apps Sync was updated!
Dear Google Apps Sync user,
Google Apps Sync for Microsoft Outlook® was recently updated. Your computer
now has the latest version (version 2.5). This release includes bug fixes
to improve product reliability. For more information about these and other
changes, please see the help article here:
http://www.google.com/support/a/bin/answer.py?answer=153463
Sincerely,
The Google Apps Sync Team.

View file

@ -0,0 +1,68 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>It's be great to talk with Jonathan-- feel free to connect us. Thanks.<br>
</p>
<p><br>
</p>
<p>The bug I mentioned manifested itself like this:<br>
</p>
<p><br>
</p>
<div class="remarkup-code-block" data-code-lang="text" data-sigil="remarkup-code-block" style="margin: 0px 0px 12px; padding: 0px; border: 0px; white-space: pre; font-family: 'Segoe UI', 'Segoe UI Web Regular', 'Segoe UI Symbol', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; line-height: 18.8500003814697px; background-color: rgb(255, 255, 255);">
<pre class="remarkup-code" style="margin-top: 0px; margin-bottom: 0px; padding: 8px; border: 1px solid rgb(233, 219, 205); overflow: auto; font-family: Menlo, Consolas, Monaco, monospace; font-stretch: normal; font-size: 10px; line-height: normal; background: rgb(253, 243, 218);">Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/gevent/greenlet.py", line 327, in run
result = self._run(*self.args, **self.kwargs)
File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 190, in _run
fail_classes=self.retry_fail_classes)
File "/vagrant/inbox/util/concurrency.py", line 120, in retry_and_report_killed
**reset_params)()
File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped
return func(*args, **kwargs)
File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 217, in _run_impl
self.state = self.state_handlers[old_state]()
File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped
return func(*args, **kwargs)
File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 270, in initial_sync
self.initial_sync_impl(crispin_client)
File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 293, in initial_sync_impl
remote_uids = crispin_client.all_uids()
File "/vagrant/inbox/crispin.py", line 489, in all_uids
fetch_result = self.conn.search(['ALL', 'UID'])
File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 588, in search
return self._search(normalise_search_criteria(criteria), charset)
File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 621, in _search
for item in parse_response(data):
File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 46, in parse_response
return tuple(gen_parsed_response(data))
File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 56, in gen_parsed_response
for token in src:
File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 118, in __iter__
for tok in self.read_token_stream(iter(source)):
File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 149, in __iter__
return PushableIterator(six.iterbytes(self.src_text))
File "/usr/local/lib/python2.7/dist-packages/imapclient/six.py", line 597, in iterbytes
return (ord(byte) for byte in buf)
TypeError: 'NoneType' object is not iterable
&lt;FolderSyncEngine at 0x5e4e550&gt; failed with TypeError</pre>
<div><br>
</div>
</div>
<p><br>
</p>
<p>But turns out Tom fixed it <a href="https://bitbucket.org/mjs0/imapclient/commits/1de7a0b63367d49587b0454804d4e29079f84f0b?at=default">
here</a>.&nbsp;&nbsp;I don't think it's yet on PyPI.<br>
</p>
<p><br>
</p>
<p><br>
</p>
<p><br>
</p>
</div>
</body>

View file

@ -0,0 +1,47 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:12pt;color:#000000;background-color:#FFFFFF;font-family:Calibri,Arial,Helvetica,sans-serif;">
<p>You should send these to team@&nbsp;<br>
</p>
<p><br>
</p>
<p>hope you feel better!&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
<div dir="ltr" style="color: rgb(33, 33, 33);">
<hr tabindex="-1" style="display:inline-block; width:98%">
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif" color="#000000" style="font-size:11pt"><b>From:</b> Andrea Whiting<br>
<b>Sent:</b> Monday, April 20, 2015 9:48 AM<br>
<b>To:</b> Christine Spang; Michael Grinich<br>
<b>Subject:</b> WFH this morn</font>
<div>&nbsp;</div>
</div>
<div>
<div id="divtagdefaultwrapper" style="font-size:12pt; color:#000000; background-color:#FFFFFF; font-family:Calibri,Arial,Helvetica,sans-serif">
<p>Morning!<br>
</p>
<p><br>
</p>
<p>I'm coughing up a post-pneumonia storm this morning, going to see if I can get a doctor's appt. in the AM and then potentially come in after lunch. My body might just need a day of rest after all that travel.<br>
</p>
<p><br>
</p>
<p>Not sure if WFH is a message to email team@ or post on slack in 'general' - lmk what works best.<br>
</p>
<p><br>
</p>
<p>Andrea<br>
</p>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,20 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:12pt;color:#000000;background-color:#FFFFFF;font-family:Calibri,Arial,Helvetica,sans-serif;">
<p>You should send these to team@&nbsp;<br>
</p>
<p><br>
</p>
<p>hope you feel better!&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
</div>
</body>

View file

@ -0,0 +1,137 @@
<html>
<head>
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Diso-8859-=
1">
<style type=3D"text/css" style=3D"display:none;"><!-- P {margin-top:0;margi=
n-bottom:0;} --></style>
</head>
<body dir=3D"ltr">
<div id=3D"divtagdefaultwrapper" style=3D"font-size:12pt;color:#000000;back=
ground-color:#FFFFFF;font-family:Calibri,Arial,Helvetica,sans-serif;">
<p>Hey All,</p>
<p><br>
</p>
<p>This was a long email but wanted to do a quick followup on the&nbsp;<a h=
ref=3D"http://www.onemedical.com/sf/doctors/?gclid=3DCJi_-9bP9sUCFUiGfgodMA=
UAxQ">One Medical</a> portion. I've only received a few responses so far.&n=
bsp;</p>
<p><br>
</p>
<p>Signing up would not require you to change your current primary care doc=
tor or pay additional insurance, rather it is a primary care center (think =
Kaiser) where you can make all of your appointments. Super convenient right=
!?</p>
<p><br>
</p>
<p>Get back to me if this is something you want to participate in. Nylas wo=
uld cover the cost.&nbsp;<br>
</p>
<p><br>
</p>
<p>Thanks,<br>
</p>
<p>Makala&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
<br>
<br>
<div style=3D"color: rgb(0, 0, 0);">
<hr tabindex=3D"-1" style=3D"display:inline-block; width:98%" customtabinde=
x=3D"-1" disabled=3D"true">
<div id=3D"divRplyFwdMsg" dir=3D"ltr"><font face=3D"Calibri, sans-serif" co=
lor=3D"#000000" style=3D"font-size:11pt"><b>From:</b> Makala Keys<br>
<b>Sent:</b> Friday, May 29, 2015 4:59 PM<br>
<b>To:</b> team<br>
<b>Subject:</b> Revisiting Health Insurance </font>
<div>&nbsp;</div>
</div>
<div>Hi All!
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit">A number of health insurance questions have come up recently. As we g=
row I want to make sure that everyone is well-informed about insurance and =
that you are getting the most out
of your health benefits. &nbsp;</span>
<div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit">My original insurance presentation
</span><a href=3D"https://docs.google.com/presentation/d/1VcKjcKNfvPS5BSfgJ=
lie9lYhFAb5U7_3ujfLHONRw6E/edit#slide=3Did.p" style=3D"font-size:15.9px; li=
ne-height:1.4" tabindex=3D"-1" disabled=3D"true">here&nbsp;</a>, explains w=
hat plans Nylas currently offers.</div>
<div>
<div><br>
</div>
<div>This week I also looked into how to set up a <a href=3D"https://docs.g=
oogle.com/document/d/1rnX8wGGkKvSEBV3kr25W9lKYnvEP_CPvOpl7R0BPK5U/edit#" ta=
bindex=3D"-1" disabled=3D"true">
health savings account</a>. A health savings account is a<span style=3D"fon=
t-size:15.9px; line-height:1.4; background-color:inherit">&nbsp;special tax=
advantaged account that is used with a high-deductible health plan (HDHP),=
and it allows you to pay for various qualified
medical expenses tax-free. Nylas offers two high-deductible health plans. =
You can see exactly which plan you are enrolled in, and if you qualify to s=
et up an HSA, through your
<a href=3D"https://secure.zenefits.com/accounts/login/" tabindex=3D"-1" dis=
abled=3D"true">
Zenefits </a>account.&nbsp;</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit">Finally, I researched info about &nbsp;<a href=3D"http://www.onemedic=
al.com/enterprise/" tabindex=3D"-1" disabled=3D"true">One Medical Group.&nb=
sp;</a>&nbsp;which is an innovate
<a href=3D"http://techcrunch.com/2014/04/17/one-medical-group-raises-40m-to=
-help-reinvent-the-doctors-office/" tabindex=3D"-1" disabled=3D"true">
primary care health organization.&nbsp;</a>&nbsp;One Medical group has seve=
ral locations in San Francisco and a location in Berkeley. It offers an abu=
ndance of services listed
<a href=3D"http://www.onemedical.com/sf/help/" tabindex=3D"-1" disabled=3D"=
true">here</a>, making it easier for you to make doctors appointments.&nbsp=
;</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit">Nylas is going to start offering One Medical Group membership on an o=
pt-in basis. Take some time to look it over this weekend and see if this is=
something you are interested in.
Our One Medical membership plan &nbsp;will also cover spouses and children=
.&nbsp;</span></div>
<div><br>
</div>
<div>As always, feel free to reach out with questions!&nbsp;</div>
<div><br>
</div>
<div>Makala&nbsp;</div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><span style=3D"font-size:15.9px; line-height:1.4; background-color:inh=
erit"><br>
</span></div>
<div><br>
</div>
<div><br>
</div>
<div>
<div><br>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,40 @@
<head>
<meta http-equiv="3D&quot;Content-Type&quot;" content="3D&quot;text/html;" charset="3Diso-8859-=" 1"="">
<style type="3D&quot;text/css&quot;" style="3D&quot;display:none;&quot;"><!-- P {margin-top:0;margi=
n-bottom:0;} --></style>
</head>
<body dir="3D&quot;ltr&quot;">
<div id="3D&quot;divtagdefaultwrapper&quot;" style="3D&quot;font-size:12pt;color:#000000;back=" ground-color:#ffffff;font-family:calibri,arial,helvetica,sans-serif;"="">
<p>Hey All,</p>
<p><br>
</p>
<p>This was a long email but wanted to do a quick followup on the&nbsp;<a h="ref=3D&quot;http://www.onemedical.com/sf/doctors/?gclid=3DCJi_-9bP9sUCFUiGfgodMA=" uaxq"="">One Medical</a> portion. I've only received a few responses so far.&amp;n=
bsp;</p>
<p><br>
</p>
<p>Signing up would not require you to change your current primary care doc=
tor or pay additional insurance, rather it is a primary care center (think =
Kaiser) where you can make all of your appointments. Super convenient right=
!?</p>
<p><br>
</p>
<p>Get back to me if this is something you want to participate in. Nylas wo=
uld cover the cost.&nbsp;<br>
</p>
<p><br>
</p>
<p>Thanks,<br>
</p>
<p>Makala&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
<br>
<br>
</div>
</body>

View file

@ -0,0 +1,49 @@
<html><head></head><body><div id="inbox-html-wrapper"
class="show-quoted-text"><meta http-equiv="Content-Type" content="text/html;
charset=utf-8"><div dir="ltr"><div class="gmail_quote"><div>Just an update on
this guys, we had three other customers login this morning and it seems that
their accounts are also listed as: "Sync not running" but some of their
messages have been synced. Emails are below:<br><br><a
href="mailto:drew@formation8.com">drew@formation8.com</a> (which is somehow
listed twice)<br><a
href="mailto:sam.kwok@garage.com">sam.kwok@garage.com</a><br><a
href="mailto:oprokhorenko@splunk.com">oprokhorenko@splunk.com</a></div><div><br></div><div>-Andrew</div><div>&nbsp;</div><blockquote
class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc
solid;padding-left:1ex"><div dir="ltr"><div><div class="gmail_quote">From: <b
class="gmail_sendername">Jeff Meister</b> <span dir="ltr">&lt;<a
href="mailto:jeff@esper.com"
target="_blank">jeff@esper.com</a>&gt;</span><br>Date: Mon, Jun 22, 2015 at
4:00 PM<br>Subject: Nylas bug reports<br>To: support &lt;<a
href="mailto:support@nylas.com"
target="_blank">support@nylas.com</a>&gt;<br>Cc: Michael Grinich &lt;<a
href="mailto:mg@nilas.com" target="_blank">mg@nilas.com</a>&gt;, Christine
Spang &lt;<a href="mailto:spang@nylas.com"
target="_blank">spang@nylas.com</a>&gt;, Kavya Joshi &lt;<a
href="mailto:kavya@nylas.com" target="_blank">kavya@nylas.com</a>&gt;, Karim
Hamidou &lt;<a href="mailto:karim@nylas.com"
target="_blank">karim@nylas.com</a>&gt;<br><br><br><div dir="ltr">Hi Nylas
folks,<br><div><br>I made a new thread because the old one was getting really
long. We've noticed a few things when onboarding our first few Exchange
customers:<br><br></div><div>1. One of our users signed in to Nylas
successfully with email <a href="mailto:oprokhorenko@splunk.com"
target="_blank">oprokhorenko@splunk.com</a>, but I see in the dashboard that
his status is "Sync not running", with a white circle rather than red or green.
We haven't seen this one before. Clicking on his entry does show 84 messages
synced, but that hasn't changed in a while. What should we
do?<br><br></div><div>2. We may be experiencing event duplication issues again,
but I'm still looking into this to see if we're causing the problem from our
side. The affected account in this case is <a href="mailto:han@formation8.com"
target="_blank">han@formation8.com</a>. I'll send an update once I have
something concrete, but I wanted to bring it up in case there are any quick
checks you can run on his account in the
meantime.<br></div><div><br></div><div>3. I noticed that drafts have started
appearing in my delta sync. Maybe it was always this way and I didn't notice
because my testing accounts didn't have a user actively emailing, but we're
getting a lot of data that we don't need. We use exclude_types in our delta
sync requests, with every type except "event", but if I add "draft" to this
list, I get a bad request error from Nylas. Can you enable filtering of drafts
here?<br><br></div><div>Thanks again, and see you on Wednesday!<span><font
color="#888888"><br></font></span></div><span><font
color="#888888"><div>Jeff<br></div></font></span></div>
</div><br></div></div>
</blockquote></div></div></div></body></html>

View file

@ -0,0 +1,7 @@
<head></head><body><div id="inbox-html-wrapper" class="show-quoted-text"><meta http-equiv="Content-Type" content="text/html;
charset=utf-8"><div dir="ltr"><div class="gmail_quote"><div>Just an update on
this guys, we had three other customers login this morning and it seems that
their accounts are also listed as: "Sync not running" but some of their
messages have been synced. Emails are below:<br><br><a href="mailto:drew@formation8.com">drew@formation8.com</a> (which is somehow
listed twice)<br><a href="mailto:sam.kwok@garage.com">sam.kwok@garage.com</a><br><a href="mailto:oprokhorenko@splunk.com">oprokhorenko@splunk.com</a></div><div><br></div><div>-Andrew</div><div>&nbsp;</div></div></div></div>
</body>

View file

@ -0,0 +1,49 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<p>Hello!</p>
<p>Here is a summary of the alerts in the past 24 hours:</p>
<table>
<tbody>
<tr>
<td><b>Alert name</b></td>
<td style="text-align:right"><b># Critical</b></td>
<td style="text-align:right"><b># Warning</b></td>
</tr>
<tr>
<td>mt-st-helena.account-dead-check</td>
<td style="text-align:right">199.0</td>
<td style="text-align:right">0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-redwoodstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">2.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-redwoodproduction-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">11.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-edgehillproduction-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">1.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-edgehillstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">9.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-mailsyncstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">9.0</td>
</tr>
</tbody>
</table>
<p>Have a good day!</p>
</body>
</html>

View file

@ -0,0 +1,48 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<p>Hello!</p>
<p>Here is a summary of the alerts in the past 24 hours:</p>
<table>
<tbody>
<tr>
<td><b>Alert name</b></td>
<td style="text-align:right"><b># Critical</b></td>
<td style="text-align:right"><b># Warning</b></td>
</tr>
<tr>
<td>mt-st-helena.account-dead-check</td>
<td style="text-align:right">199.0</td>
<td style="text-align:right">0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-redwoodstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">2.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-redwoodproduction-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">11.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-edgehillproduction-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">1.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-edgehillstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">9.0</td>
</tr>
<tr>
<td>mt-st-helena.mysql-mailsyncstaging-check</td>
<td style="text-align:right">0</td>
<td style="text-align:right">9.0</td>
</tr>
</tbody>
</table>
<p>Have a good day!</p>
</body>

View file

@ -0,0 +1,255 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>Thanks for coming by today. :) Let me know next time you're around and settled and I'll show you a wicked awesome demo of the stuff we've been building!&nbsp;<br>
</p>
<p><br>
</p>
<p>And congrats on YC!&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
<div style="color: rgb(33, 33, 33);">
<hr tabindex="-1" style="display:inline-block; width:98%">
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif" color="#000000" style="font-size:11pt"><b>From:</b> Ted Benson &lt;eob@csail.mit.edu&gt;<br>
<b>Sent:</b> Monday, April 27, 2015 10:51 AM<br>
<b>To:</b> Michael Grinich<br>
<b>Subject:</b> Re: James Tamplin intro?</font>
<div>&nbsp;</div>
</div>
<div>Hey Michael,<br>
<br>
We're in you're neck of the woods and can meet whenever you're free -- or happy to swing by at 12 like planned if that fits your schedule better.
<br>
<br>
Looking forward to it!<br>
Ted<br>
<div class="gmail_quote">On Fri, Apr 24, 2015 at 11:55 AM Michael Grinich &lt;<a href="mailto:mg@nylas.com">mg@nylas.com</a>&gt; wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex; border-left:1px #ccc solid; padding-left:1ex">
<div dir="ltr">
<div style="font-size:10pt; color:#000000; background-color:#ffffff; font-family:Arial,Helvetica,sans-serif">
<p>Sure-- bring him along too. See you then. :)<br>
</p>
<p><br>
</p>
<p>--mg<br>
</p>
<p><br>
</p>
<div style="color:rgb(33,33,33)">
<hr style="display:inline-block; width:98%">
<div dir="ltr"><font face="Calibri, sans-serif" color="#000000" style="font-size:11pt"><b>From:</b>
<a href="mailto:edward.benson@gmail.com" target="_blank">edward.benson@gmail.com</a> &lt;<a href="mailto:edward.benson@gmail.com" target="_blank">edward.benson@gmail.com</a>&gt; on behalf of Ted Benson &lt;<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>&gt;<br>
<b>Sent:</b> Friday, April 24, 2015 11:39 AM<br>
<b>To:</b> Michael Grinich<br>
<b>Subject:</b> Re: James Tamplin intro?</font>
<div>&nbsp;</div>
</div>
</div>
</div>
</div>
<div dir="ltr">
<div style="font-size:10pt; color:#000000; background-color:#ffffff; font-family:Arial,Helvetica,sans-serif">
<div style="color:rgb(33,33,33)">
<div>
<div dir="ltr">
<div>w/ James: Thanks a bunch!</div>
<div><br>
</div>
<div>Lunch: Great! Just sent you an invite. Noon OK?</div>
<div><br>
</div>
<div>If YC rejection: That doesn't worry me. We're doing this either way. And if we truly hit a brick wall then that's valuable data for us as well.</div>
<div><br>
</div>
<div>Looking forward to seeing you! Mind if Jake (cofounder) comes along for lunch?</div>
<div><br>
</div>
<br>
</div>
<div class="gmail_extra"><br>
<div class="gmail_quote">On Fri, Apr 24, 2015 at 12:31 AM, Michael Grinich <span dir="ltr">
&lt;<a href="mailto:mg@nylas.com" target="_blank">mg@nylas.com</a>&gt;</span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex; border-left:1px #ccc solid; padding-left:1ex">
<div>Cool, I'll see what I can do wrt James. He might be in Google land these days.&nbsp;<br>
<div><br>
Some VCs see YC rejection as a red flag. You likely need to have clearly demonstrated conviction and/or traction to raise if that happens.</div>
<div><br>
</div>
<div>Want to stop by for lunch on Monday? We're at <a>2030 Harrison St. in SF</a>.&nbsp;</div>
<div>
<div><br>
<br>
<br>
<br>
<div class="gmail_quote">On Wed, Apr 22, 2015 at 4:21 PM -0700, &quot;Ted Benson&quot; <span dir="ltr">
&lt;<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>&gt;</span> wrote:<br>
<br>
</div>
<div>
<div dir="ltr">Hi Michael,
<div><br>
</div>
<div>No worries about the delay! I've never been to PyCon but heard it's a good time.</div>
<div><br>
</div>
<div>Thanks -- I know it's just the beginning, but it feels like such a long road to have gotten this far already. Psyched for what's to come.</div>
<div><br>
</div>
<div>Connections:</div>
<div>Right now we're looking for advice and angel investment.&nbsp;</div>
<div>- If we don't get YC, we're going to start a seed round</div>
<div>- If we do get YC, we're still really interested in meeting the kinds of people who can offer advice having already grown successful platform companies</div>
<div><br>
</div>
<div>In your judgement is that a reasonable way to plan out meetings for the post-YC days we're there.&nbsp;</div>
<div><br>
</div>
<div>Would love an intro to James, his tech counterpart, or others you think could help us out along those lines! (Agree about their model, btw).</div>
<div><br>
</div>
<div>I'd love to drop by Monday or Tuesday and see what you all are cooking up!</div>
<div><br>
</div>
<div>Thanks again!</div>
<div>Ted</div>
<div><br>
</div>
<div>&nbsp; &nbsp;</div>
</div>
<div class="gmail_extra"><br>
<div class="gmail_quote">On Wed, Apr 22, 2015 at 5:03 AM, Michael Grinich <span dir="ltr">
&lt;<a href="mailto:mgrinich@gmail.com" target="_blank">mgrinich@gmail.com</a>&gt;</span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex; border-left:1px #ccc solid; padding-left:1ex">
<br>
Hey Ted-- sorry for the delay. I was at PyCon in Montreal (we sponsored this year) and then our company retreat in Tahoe. Finally catching up back in SF.
<br>
<br>
Congrats on making it to the interview circuit!<br>
regarding networking, are you looking for partnerships or YC advise or investment or something else?<br>
<br>
I've actually been coaching a couple startups, one of whichever is pitching YC this wkd. Anything specific you need there?<br>
<br>
Our first engineer came from Firebase (and MIT/PDOS before that), so I could get an intro to James. FYI he's more on the biz side since he's not a programmer. Also I'm not sure they're the best model for success honestly.
<br>
<br>
In any case, want to grab a coffee and catch up? I'd love to show you some of the stuff we're building too. (The good stuff we haven't announced yet.) Happy to help on any of the mentioned points above.
<br>
<span><font color="#888888"><br>
--mg</font></span>
<div>
<div><br>
<div class="gmail_quote">On Sun, Apr 19, 2015 at 8:45 AM Ted Benson &lt;<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>&gt; wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex; border-left:1px #ccc solid; padding-left:1ex">
<div dir="ltr">Hah - I just realized I might have read Facebook wrong. It is WE who are friends, not you and James.
<div><br>
</div>
<div>So I'll spin that ask around: do you know any good advisors or angels that you'd be comfortable putting us in touch with? Nearest neighbors to us are companies like IFTTT, Firebase, Ionic, Parse, Automattic.</div>
</div>
<div class="gmail_extra"><br>
<div class="gmail_quote">On Sun, Apr 19, 2015 at 11:39 AM, Ted Benson <span dir="ltr">
&lt;<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>&gt;</span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex; border-left:1px #ccc solid; padding-left:1ex">
<div dir="ltr">Hey Michael,
<div><br>
</div>
<div>Hope all is well! FB says your friends with James Tamplin. Do you know him well enough to introduce me to him so I could ask for some advice?</div>
<div><br>
</div>
<div>My cofounder and I are headed out to YC to interview next weekend, and we're trying to make the most of the trip. Firebase and James' other involvements are right up the alley of what we're doing, so I think he would be a really good person to meet.&nbsp;</div>
<div><br>
</div>
<div>Company quickie, for context:</div>
<div><br>
<span style="color:rgb(0,0,0); font-family:Arial; font-size:12.8000001907349px; white-space:pre-wrap">Cloudstitch.io is a beginner-friendly reactive platform for making web apps that run off of everyday objects. Objects like Google Spreadsheets and Docs, all
the way to physical objects like sensors and Amazon Dash-style buttons. Think IFTTT but for building apps.</span><br>
<div>
<div><br>
</div>
<div>Let me know -- and thanks either way!&nbsp;</div>
<div><br>
</div>
<div>Cheers,</div>
<div>Ted</div>
<span><font color="#888888">
<div><br>
</div>
<div><br>
</div>
-- <br>
<div>
<div dir="ltr">:: Ted Benson<br>
::&nbsp;<a href="http://people.csail.mit.edu/eob/" target="_blank">http://people.csail.mit.edu/eob/</a><br>
<div>:: @edwardbenson</div>
</div>
</div>
</font></span></div>
</div>
</div>
</blockquote>
</div>
<br>
<br clear="all">
<div><br>
</div>
-- <br>
<div>
<div dir="ltr">:: Ted Benson<br>
::&nbsp;<a href="http://people.csail.mit.edu/eob/" target="_blank">http://people.csail.mit.edu/eob/</a><br>
<div>:: @edwardbenson</div>
</div>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</blockquote>
</div>
<br>
<br clear="all">
<div><br>
</div>
-- <br>
<div>
<div dir="ltr">:: Ted Benson<br>
::&nbsp;<a href="http://people.csail.mit.edu/eob/" target="_blank">http://people.csail.mit.edu/eob/</a><br>
<div>:: @edwardbenson</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</blockquote>
</div>
<br>
<br clear="all">
<div><br>
</div>
-- <br>
<div>
<div dir="ltr">:: Ted Benson<br>
::&nbsp;<a href="http://people.csail.mit.edu/eob/" target="_blank">http://people.csail.mit.edu/eob/</a><br>
<div>:: @edwardbenson</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</blockquote>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,20 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>Thanks for coming by today. :) Let me know next time you're around and settled and I'll show you a wicked awesome demo of the stuff we've been building!&nbsp;<br>
</p>
<p><br>
</p>
<p>And congrats on YC!&nbsp;<br>
</p>
<p><br>
</p>
<p><br>
</p>
</div>
</body>

View file

@ -0,0 +1,58 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>What about Haystack?&nbsp;https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf<br>
</p>
<p><br>
</p>
<p><br>
</p>
<div style="color: rgb(0, 0, 0);">
<div style="text-align: left;"><br>
</div>
<hr tabindex="-1" style="display:inline-block; width:98%">
<div id="divRplyFwdMsg" dir="ltr"><font face="Calibri, sans-serif" color="#000000" style="font-size:11pt"><b>From:</b> Christine Spang<br>
<b>Sent:</b> Thursday, May 21, 2015 6:52 PM<br>
<b>To:</b> Nylas Study Group<br>
<b>Subject:</b> Intros &amp; paper suggestions</font>
<div>&nbsp;</div>
</div>
<div>hey folks,
<div><br>
</div>
<div>Thought I'd let y'all know who's on this list. Current list members are:</div>
<div><br>
</div>
<div><b>Nylas team</b></div>
<div>Michael Grinich</div>
<div>me</div>
<div>Kavya Joshi</div>
<div>Eben Freeman</div>
<div>Jennie Lees</div>
<div>Karim Hamidou</div>
<div>Evan Morikawa</div>
<div>Ben Gotow</div>
<div>Rob McQueen</div>
<div>Kartik Talwar</div>
<div>Andrea Whiting</div>
<div>Makala Keys</div>
<div><br>
</div>
<div><b>Friends</b></div>
<div>Nelson Elhage</div>
<div>Deborah Hanus</div>
<div>Owen Derby</div>
<div>Marco Munizaga</div>
<div><br>
</div>
<div>Anyone have a paper they're really hankering to read or present in two weeks? :)</div>
<div>--spang</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
<style type="text/css" style="display:none;"><!-- P {margin-top:0;margin-bottom:0;} --></style>
</head>
<body dir="ltr">
<div id="divtagdefaultwrapper" style="font-size:10pt;color:#000000;background-color:#FFFFFF;font-family:Arial,Helvetica,sans-serif;">
<p>What about Haystack?&nbsp;https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf<br>
</p>
<p><br>
</p>
<p><br>
</p>
</div>
</body>

View file

@ -0,0 +1,34 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<div dir="ltr">Hi Christine,&nbsp;
<div><br>
</div>
<div>My apologies, please use the below referral code when taking the Insights evaluation:&nbsp;</div>
<div><br>
</div>
<div>
<p style="font-size:13px;margin:0px;font-family:Times;color:rgb(18,85,204)"><span style="font-family:Arial;letter-spacing:0px;color:rgb(35,35,35)"><b>Go To</b>:&nbsp;&nbsp;<a href="https://online.insights.com/evaluator/SNP/discovery" target="_blank"><span style="font-family:Times;letter-spacing:0px;color:rgb(18,85,204)">https://online.insights.com/evaluator/SNP/discovery</span></a>&nbsp;
&nbsp;&nbsp;</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><b>Referral Code</b>: Nylas2015</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><br>
</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Please let us know if you have any questions!</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><br>
</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Thank you,</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Eva</span></p>
</div>
<div class="gmail_extra"><br>
<div class="gmail_quote"><br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div dir="ltr"><span class="HOEnZb"><font color="#888888"></font></span></div>
</blockquote>
</div>
<br>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,31 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
</head>
<body>
<div dir="ltr">Hi Christine,&nbsp;
<div><br>
</div>
<div>My apologies, please use the below referral code when taking the Insights evaluation:&nbsp;</div>
<div><br>
</div>
<div>
<p style="font-size:13px;margin:0px;font-family:Times;color:rgb(18,85,204)"><span style="font-family:Arial;letter-spacing:0px;color:rgb(35,35,35)"><b>Go To</b>:&nbsp;&nbsp;<a href="https://online.insights.com/evaluator/SNP/discovery" target="_blank"><span style="font-family:Times;letter-spacing:0px;color:rgb(18,85,204)">https://online.insights.com/evaluator/SNP/discovery</span></a>&nbsp;
&nbsp;&nbsp;</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><b>Referral Code</b>: Nylas2015</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><br>
</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Please let us know if you have any questions!</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px"><br>
</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Thank you,</span></p>
<p style="font-size:13px;margin:0px;font-family:Arial;color:rgb(35,35,35)"><span style="letter-spacing:0px">Eva</span></p>
</div>
<div class="gmail_extra"><br>
<div class="gmail_quote"><br>
</div>
<br>
</div>
</div>
</body>

View file

@ -0,0 +1,3 @@
Here is another email
Sent from my BlackBerry

View file

@ -0,0 +1,22 @@
test 2 this should list second
and have spaces
and retain this formatting
- how about bullets
- and another
On Fri, Feb 24, 2012 at 10:19 AM, <examples@email.goalengine.com> wrote:
> Give us an example of how you applied what they learned to achieve
> something in your organization
--
*Joe Smith | Director, Product Management*

View file

@ -0,0 +1,3 @@
Here is another email
Sent from my iPhone

View file

@ -0,0 +1,3 @@
Here is another email
Sent from my Verizon Wireless BlackBerry

View file

@ -0,0 +1,3 @@
Here is another email
Sent from my desk, is much easier then my mobile phone.

View file

@ -0,0 +1,43 @@
_ = require('underscore')
fs = require('fs')
path = require 'path'
QuotedHTMLParser = require('../src/services/quoted-html-parser')
describe "QuotedHTMLParser", ->
readFile = (fname) ->
emailPath = path.resolve(__dirname, 'fixtures', 'emails', fname)
return fs.readFileSync(emailPath, 'utf8')
hideQuotedHTML = (fname) ->
return QuotedHTMLParser.hideQuotedHTML(readFile(fname))
removeQuotedHTML = (fname) ->
return QuotedHTMLParser.removeQuotedHTML(readFile(fname))
numQuotes = (html) ->
re = new RegExp(QuotedHTMLParser.annotationClass, 'g')
html.match(re)?.length ? 0
[1..15].forEach (n) ->
it "properly parses email_#{n}", ->
expect(removeQuotedHTML("email_#{n}.html")).toEqual readFile("email_#{n}_stripped.html")
# We have a little utility method that you can manually uncomment to
# generate what the current iteration of the QuotedHTMLParser things the
# `removeQuotedHTML` should look like. These can be manually inspected in
# a browser before getting their filename changed to
# `email_#{n}_stripped.html". The actually tests will run the current
# iteration of the `removeQuotedHTML` against these files to catch if
# anything has changed in the parser.
#
# It's inside of the specs here instaed of its own script because the
# `QuotedHTMLParser` needs Electron booted up in order to work because
# of the DOMParser.
xit "Run this simple funciton to generate output files", ->
[1..15].forEach (n) ->
newHTML = QuotedHTMLParser.removeQuotedHTML(readFile("email_#{n}.html"))
outPath = path.resolve(__dirname, 'fixtures', 'emails', "email_#{n}_raw_stripped.html")
fs.writeFileSync(outPath, newHTML)

View file

@ -0,0 +1,289 @@
# This is modied from https://github.com/mko/emailreplyparser, which is a
# JS port of GitHub's Ruby https://github.com/github/email_reply_parser
fs = require('fs')
path = require 'path'
_ = require('underscore')
QuotedPlainTextParser = require('../src/services/quoted-plain-text-parser')
getParsedEmail = (name, format="plain") ->
data = getRawEmail(name, format)
reply = QuotedPlainTextParser.parse data, format
reply._setHiddenState()
return reply
getRawEmail = (name, format="plain") ->
emailPath = path.resolve(__dirname, 'fixtures', 'emails', "#{name}.txt")
return fs.readFileSync(emailPath, "utf8")
deepEqual = (expected=[], test=[]) ->
for toExpect, i in expected
expect(test[i]).toBe toExpect
describe "QuotedPlainTextParser", ->
it "reads_simple_body", ->
reply = getParsedEmail('email_1_1')
expect(reply.fragments.length).toBe 3
deepEqual [
false
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
true
true
], _.map(reply.fragments, (f) ->
f.signature
)
deepEqual [
false
true
true
], _.map(reply.fragments, (f) ->
f.hidden
)
expect(reply.fragments[0].to_s()).toEqual 'Hi folks\n\nWhat is the best way to clear a Riak bucket of all key, values after\nrunning a test?\nI am currently using the Java HTTP API.'
expect(reply.fragments[1].to_s()).toEqual '-Abhishek Kona'
it "reads_top_post", ->
reply = getParsedEmail('email_1_3')
expect(reply.fragments.length).toEqual 5
deepEqual [
false
false
true
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
true
true
true
true
], _.map(reply.fragments, (f) ->
f.hidden
)
deepEqual [
false
true
false
false
true
], _.map(reply.fragments, (f) ->
f.signature
)
expect(new RegExp('^Oh thanks.\n\nHaving').test(reply.fragments[0].to_s())).toBe true
expect(new RegExp('^-A').test(reply.fragments[1].to_s())).toBe true
expect(/^On [^\:]+\:/m.test(reply.fragments[2].to_s())).toBe true
expect(new RegExp('^_').test(reply.fragments[4].to_s())).toBe true
it "reads_bottom_post", ->
reply = getParsedEmail('email_1_2')
expect(reply.fragments.length).toEqual 6
deepEqual [
false
true
false
true
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
false
false
false
false
true
], _.map(reply.fragments, (f) ->
f.signature
)
deepEqual [
false
false
false
true
true
true
], _.map(reply.fragments, (f) ->
f.hidden
)
expect(reply.fragments[0].to_s()).toEqual 'Hi,'
expect(new RegExp('^On [^:]+:').test(reply.fragments[1].to_s())).toBe true
expect(/^You can list/m.test(reply.fragments[2].to_s())).toBe true
expect(/^> /m.test(reply.fragments[3].to_s())).toBe true
expect(new RegExp('^_').test(reply.fragments[5].to_s())).toBe true
it "reads_inline_replies", ->
reply = getParsedEmail('email_1_8')
expect(reply.fragments.length).toEqual 7
deepEqual [
true
false
true
false
true
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
false
false
false
false
false
true
], _.map(reply.fragments, (f) ->
f.signature
)
deepEqual [
false
false
false
false
true
true
true
], _.map(reply.fragments, (f) ->
f.hidden
)
expect(new RegExp('^On [^:]+:').test(reply.fragments[0].to_s())).toBe true
expect(/^I will reply/m.test(reply.fragments[1].to_s())).toBe true
expect(/^> /m.test(reply.fragments[2].to_s())).toBe true
expect(/^and under this./m.test(reply.fragments[3].to_s())).toBe true
expect(/^> /m.test(reply.fragments[4].to_s())).toBe true
expect(reply.fragments[5].to_s().trim()).toEqual ''
expect(new RegExp('^-').test(reply.fragments[6].to_s())).toBe true
it "recognizes_date_string_above_quote", ->
reply = getParsedEmail('email_1_4')
expect(/^Awesome/.test(reply.fragments[0].to_s())).toBe true
expect(/^On/m.test(reply.fragments[1].to_s())).toBe true
expect(/Loader/m.test(reply.fragments[1].to_s())).toBe true
it "a_complex_body_with_only_one_fragment", ->
reply = getParsedEmail('email_1_5')
expect(reply.fragments.length).toEqual 1
it "reads_email_with_correct_signature", ->
reply = getParsedEmail('correct_sig')
expect(reply.fragments.length).toEqual 2
deepEqual [
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
true
], _.map(reply.fragments, (f) ->
f.signature
)
deepEqual [
false
true
], _.map(reply.fragments, (f) ->
f.hidden
)
expect(new RegExp('^-- \nrick').test(reply.fragments[1].to_s())).toBe true
it "deals_with_multiline_reply_headers", ->
reply = getParsedEmail('email_1_6')
expect(new RegExp('^I get').test(reply.fragments[0].to_s())).toBe true
expect(/^On/m.test(reply.fragments[1].to_s())).toBe true
expect(new RegExp('Was this').test(reply.fragments[1].to_s())).toBe true
it "does_not_modify_input_string", ->
original = 'The Quick Brown Fox Jumps Over The Lazy Dog'
QuotedPlainTextParser.parse original
expect(original).toEqual 'The Quick Brown Fox Jumps Over The Lazy Dog'
it "returns_only_the_visible_fragments_as_a_string", ->
reply = getParsedEmail('email_2_1')
String::rtrim = ->
@replace /\s*$/g, ''
fragments = _.select(reply.fragments, (f) ->
!f.hidden
)
fragments = _.map(fragments, (f) ->
f.to_s()
)
expect(reply.visibleText(format: "plain")).toEqual fragments.join('\n').rtrim()
it "parse_out_just_top_for_outlook_reply", ->
body = getRawEmail('email_2_1')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'Outlook with a reply'
it "parse_out_sent_from_iPhone", ->
body = getRawEmail('email_iPhone')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'Here is another email'
it "parse_out_sent_from_BlackBerry", ->
body = getRawEmail('email_BlackBerry')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'Here is another email'
it "parse_out_send_from_multiword_mobile_device", ->
body = getRawEmail('email_multi_word_sent_from_my_mobile_device')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'Here is another email'
it "do_not_parse_out_send_from_in_regular_sentence", ->
body = getRawEmail('email_sent_from_my_not_signature')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'Here is another email\n\nSent from my desk, is much easier then my mobile phone.'
it "retains_bullets", ->
body = getRawEmail('email_bullets')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual 'test 2 this should list second\n\nand have spaces\n\nand retain this formatting\n\n\n - how about bullets\n - and another'
it "visibleText", ->
body = getRawEmail('email_1_2')
expect(QuotedPlainTextParser.visibleText(body, format: "plain")).toEqual QuotedPlainTextParser.parse(body).visibleText(format: "plain")
it "correctly_reads_top_post_when_line_starts_with_On", ->
reply = getParsedEmail('email_1_7')
expect(reply.fragments.length).toEqual 5
deepEqual [
false
false
true
false
false
], _.map(reply.fragments, (f) ->
f.quoted
)
deepEqual [
false
true
true
true
true
], _.map(reply.fragments, (f) ->
f.hidden
)
deepEqual [
false
true
false
false
true
], _.map(reply.fragments, (f) ->
f.signature
)
expect(new RegExp('^Oh thanks.\n\nOn the').test(reply.fragments[0].to_s())).toBe true
expect(new RegExp('^-A').test(reply.fragments[1].to_s())).toBe true
expect(/^On [^\:]+\:/m.test(reply.fragments[2].to_s())).toBe true
expect(new RegExp('^_').test(reply.fragments[4].to_s())).toBe true

File diff suppressed because one or more lines are too long

View file

@ -217,37 +217,6 @@ Utils =
return -1
stripQuotedText: (html) ->
return html if Utils.quotedTextIndex(html) is -1
# Split the email into lines and remove lines that begin with > or &gt;
lines = html.split(/(\n|<br[^>]*>)/)
# Remove lines that are newlines - we'll add them back in when we join.
# We had to break them out because we want to preserve <br> elements.
lines = _.reject lines, (line) -> line == '\n'
regexs = [
/\n[ ]*(>|&gt;)/, # Plaintext lines beginning with >
/<[br|p][ ]*>[\n]?[ ]*[>|&gt;]/i, # HTML lines beginning with >
/[\n|>]On .* wrote:[\n|<]/, #On ... wrote: on it's own line
]
for ii in [lines.length-1..0] by -1
continue if not lines[ii]?
for regex in regexs
# Never remove a line with a blockquote start tag, because it
# quotes multiple lines, not just the current line!
if lines[ii].match("<blockquote")
break
if lines[ii].match(regex)
lines.splice(ii,1)
# Remove following line if its just a spacer-style element
lines.splice(ii,1) if lines[ii]?.match('<br[^>]*>')?[0] is lines[ii]
break
# Return remaining compacted email body
lines.join('\n')
# Returns true if the message contains "Forwarded" or "Fwd" in the first
# 250 characters. A strong indicator that the quoted text should be
# shown. Needs to be limited to first 250 to prevent replies to

View file

@ -0,0 +1,101 @@
_ = require 'underscore'
class QuotedHTMLParser
annotationClass: "nylas-quoted-text-segment"
# Given an html string, it will add the `annotationClass` to the DOM
# element
hideQuotedHTML: (html) ->
doc = @_parseHTML(html)
quoteElements = @_findQuoteLikeElements(doc)
@_annotateElements(quoteElements)
return doc.children[0].innerHTML
hasQuotedHTML: (html) ->
doc = @_parseHTML(html)
quoteElements = @_findQuoteLikeElements(doc)
return quoteElements.length > 0
removeQuotedHTML: (html) ->
doc = @_parseHTML(html)
quoteElements = @_findQuoteLikeElements(doc)
@_removeQuoteElements(quoteElements)
return doc.children[0].innerHTML
restoreAnnotatedHTML: (html) ->
doc = @_parseHTML(html)
quoteElements = @_findAnnotatedElements(doc)
@_removeAnnotation(quoteElements)
return doc.children[0].innerHTML
_parseHTML: (text) ->
# The `DOMParser` is VERY fast. Some benchmarks on MacBook Pro 2.3GHz
# i7:
#
# On an 1k email it took 0.13ms
# On an 88k real-world large-email it takes ~4ms
# On a million-character wikipedia page on Barack Obama it takes ~30ms
domParser = new DOMParser()
doc = domParser.parseFromString(text, "text/html")
_findQuoteLikeElements: (doc) ->
parsers = [
@_findGmailQuotes
@_findOffice365Quotes
@_findBlockquoteQuotes
]
quoteElements = []
for parser in parsers
quoteElements = quoteElements.concat(parser(doc) ? [])
return _.compact(_.uniq(quoteElements))
_findAnnotatedElements: (doc) ->
Array::slice.call(doc.getElementsByClassName(@annotationClass))
_annotateElements: (elements=[]) ->
for el in elements
el.classList.add(@annotationClass)
originalDisplay = el.style.display
el.style.display = "none"
el.setAttribute("data-nylas-quoted-text-original-display", originalDisplay)
_removeAnnotation: (elements=[]) ->
for el in elements
el.classList.remove(@annotationClass)
originalDisplay = el.getAttribute("data-nylas-quoted-text-original-display")
el.style.display = originalDisplay
el.removeAttribute("data-nylas-quoted-text-original-display")
_removeQuoteElements: (elements=[]) ->
for el in elements
try
if el.parentNode then el.parentNode.removeChild(el)
catch
# This can happen if we've already removed ourselves from the node
# or it no longer exists
continue
_findGmailQuotes: (doc) ->
# There can sometimes be `div.gmail_quote` that are false positives.
return Array::slice.call(doc.querySelectorAll('blockquote.gmail_quote'))
_findOffice365Quotes: (doc) ->
elements = doc.querySelectorAll('#divRplyFwdMsg, #OLK_SRC_BODY_SECTION')
elements = Array::slice.call(elements)
weirdEl = doc.getElementById('3D"divRplyFwdMsg"')
if weirdEl then elements.push(weirdEl)
elements = _.map elements, (el) ->
if el.previousElementSibling.nodeName is "HR"
return el.parentElement
else return el
return elements
_findBlockquoteQuotes: (doc) ->
return Array::slice.call(doc.querySelectorAll('blockquote'))
module.exports = new QuotedHTMLParser

View file

@ -0,0 +1,190 @@
_ = require 'underscore'
_str = require 'underscore.string'
# Parses plain text emails to find quoted text and signatures.
#
# For plain text emails we look for lines that look like they're quoted
# text based on common conventions:
#
# For HTML emails use QuotedHTMLParser
#
# This is modied from https://github.com/mko/emailreplyparser, which is a
# JS port of GitHub's Ruby https://github.com/github/email_reply_parser
QuotedPlainTextParser =
parse: (text) ->
parsedEmail = new ParsedEmail
parsedEmail.parse text
visibleText: (text, {showQuoted, showSignature}={}) ->
showQuoted ?= false
showSignature ?= false
@parse(text).visibleText({showQuoted, showSignature})
hiddenText: (text, {showQuoted, showSignature}={}) ->
showQuoted ?= false
showSignature ?= false
@parse(text).hiddenText({showQuoted, showSignature})
hasQuotedHTML: (text) ->
return @parse(text).hasQuotedHTML()
chomp = ->
@replace /(\n|\r)+$/, ''
# An ParsedEmail instance contains various `Fragment`s that indicate if we
# think a section of text is quoted or is a signature
class ParsedEmail
constructor: ->
@fragments = []
@currentFragment = null
return
fragments: []
hasQuotedHTML: ->
for fragment in @fragments
return true if fragment.quoted
return false
visibleText: ({showSignature, showQuoted}={}) ->
@_setHiddenState({showSignature, showQuoted})
return _.reject(@fragments, (f) -> f.hidden).map((f) -> f.to_s()).join('\n')
hiddenText: ({showSignature, showQuoted}={}) ->
@_setHiddenState({showSignature, showQuoted})
return _.filter(@fragments, (f) -> f.hidden).map((f) -> f.to_s()).join('\n')
# We set a hidden state just so we can test the expected output in our
# specs. The hidden state is determined by the requested view parameters
# and the `quoted` flag on each `fragment`
_setHiddenState: ({showSignature, showQuoted}={}) ->
fragments = _.reject @fragments, (f) ->
if f.to_s().trim() is ""
f.hidden = true
return true
else return false
for fragment, i in fragments
fragment.hidden = true
if fragment.quoted
if showQuoted or (fragments[i+1]? and not (fragments[i+1].quoted or fragments[i+1].signature))
fragment.hidden = false
continue
else continue
if fragment.signature
if showSignature
fragment.hidden = false
continue
else continue
fragment.hidden = false
parse: (text) ->
# This instance variable points to the current Fragment. If the matched
# line fits, it should be added to this Fragment. Otherwise, finish it
# and start a new Fragment.
@currentFragment = null
@_parsePlain(text)
_parsePlain: (text) ->
# Check for multi-line reply headers. Some clients break up
# the "On DATE, NAME <EMAIL> wrote:" line into multiple lines.
patt = /^(On\s(\n|.)*wrote:)$/m
doubleOnPatt = /^(On\s(\n|.)*(^(> )?On\s)((\n|.)*)wrote:)$/m
if patt.test(text) and !doubleOnPatt.test(text)
replyHeader = patt.exec(text)[0]
# Remove all new lines from the reply header.
text = text.replace(replyHeader, replyHeader.replace(/\n/g, ' '))
# The text is reversed initially due to the way we check for hidden
# fragments.
text = _str.reverse(text)
# Use the StringScanner to pull out each line of the email content.
lines = text.split('\n')
for i of lines
@_scanPlainLine lines[i]
# Finish up the final fragment. Finishing a fragment will detect any
# attributes (hidden, signature, reply), and join each line into a
# string.
@_finishFragment()
# Now that parsing is done, reverse the order.
@fragments.reverse()
return @
_signatureRE:
/(--|__|^-\w)|(^sent from my (\s*\w+){1,3}$)/i
# NOTE: Plain lines are scanned bottom to top. We reverse the text in
# `_parsePlain`
_scanPlainLine: (line) ->
line = chomp.apply(line)
if !new RegExp(@_signatureRE).test(_str.reverse(line))
line = _str.ltrim(line)
# Mark the current Fragment as a signature if the current line is ''
# and the Fragment starts with a common signature indicator.
if @currentFragment != null and line == ''
if new RegExp(@_signatureRE).test(_str.reverse(@currentFragment.lines[@currentFragment.lines.length - 1]))
@currentFragment.signature = true
@_finishFragment()
# We're looking for leading `>`'s to see if this line is part of a
# quoted Fragment.
isQuoted = new RegExp('(>+)$').test(line)
# If the line matches the current fragment, add it. Note that a common
# reply header also counts as part of the quoted Fragment, even though
# it doesn't start with `>`.
if @currentFragment != null and (@currentFragment.quoted == isQuoted or @currentFragment.quoted and (@_quoteHeader(line) or line == ''))
@currentFragment.lines.push line
else
@_finishFragment()
@currentFragment = new Fragment(isQuoted, line, "plain")
return
_quoteHeader: (line) ->
new RegExp('^:etorw.*nO$').test line
_finishFragment: ->
if @currentFragment != null
@currentFragment.finish()
@fragments.push @currentFragment
@currentFragment = null
return
# Represents a group of paragraphs in the email sharing common attributes.
# Paragraphs should get their own fragment if they are a quoted area or a
# signature.
class Fragment
constructor: (@quoted, firstLine) ->
@signature = false
@hidden = false
@lines = [ firstLine ]
@content = null
@lines = @lines.filter(->
true
)
return
content: null
finish: ->
@content = @lines.join("\n")
@lines = []
@content = _str.reverse(@content)
return
to_s: ->
@content.toString().trim()
module.exports = QuotedPlainTextParser

View file

@ -63,16 +63,4 @@
border: 0;
}
.gmail_quote,
#divRplyFwdMsg,
blockquote {
display:none;
}
.show-quoted-text .gmail_quote,
.show-quoted-text #divRplyFwdMsg,
.show-quoted-text blockquote {
display:inherit;
}
}