mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-29 11:52:34 +08:00
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:
parent
c306f553a4
commit
e8912e0e2e
61 changed files with 4140 additions and 164 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
|
|
4
spec-nylas/fixtures/emails/correct_sig.txt
Executable file
4
spec-nylas/fixtures/emails/correct_sig.txt
Executable file
|
@ -0,0 +1,4 @@
|
|||
this is an email with a correct -- signature.
|
||||
|
||||
--
|
||||
rick
|
1730
spec-nylas/fixtures/emails/email_1.html
Normal file
1730
spec-nylas/fixtures/emails/email_1.html
Normal file
File diff suppressed because it is too large
Load diff
22
spec-nylas/fixtures/emails/email_10.html
Normal file
22
spec-nylas/fixtures/emails/email_10.html
Normal 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! <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
|
||||
<makala@nylas.com> 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). </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>+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, <br>
|
||||
Makala </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>
|
3
spec-nylas/fixtures/emails/email_10_stripped.html
Normal file
3
spec-nylas/fixtures/emails/email_10_stripped.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<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! <br> <br>
|
||||
</body>
|
1
spec-nylas/fixtures/emails/email_11.html
Normal file
1
spec-nylas/fixtures/emails/email_11.html
Normal 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>
|
2
spec-nylas/fixtures/emails/email_11_stripped.html
Normal file
2
spec-nylas/fixtures/emails/email_11_stripped.html
Normal 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>
|
70
spec-nylas/fixtures/emails/email_12.html
Normal file
70
spec-nylas/fixtures/emails/email_12.html
Normal 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, you’ll 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>
|
49
spec-nylas/fixtures/emails/email_12_stripped.html
Normal file
49
spec-nylas/fixtures/emails/email_12_stripped.html
Normal 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, you’ll 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>
|
56
spec-nylas/fixtures/emails/email_13.html
Normal file
56
spec-nylas/fixtures/emails/email_13.html
Normal 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, you’ll 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>
|
12
spec-nylas/fixtures/emails/email_13_stripped.html
Normal file
12
spec-nylas/fixtures/emails/email_13_stripped.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<head></head><body>
|
||||
<div>
|
||||
Oh thanks. <br>
|
||||
|
||||
Having the function would be great. <br>
|
||||
|
||||
-Abhishek Kona <br>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</body>
|
1
spec-nylas/fixtures/emails/email_14.html
Normal file
1
spec-nylas/fixtures/emails/email_14.html
Normal file
File diff suppressed because one or more lines are too long
2
spec-nylas/fixtures/emails/email_14_stripped.html
Normal file
2
spec-nylas/fixtures/emails/email_14_stripped.html
Normal file
File diff suppressed because one or more lines are too long
102
spec-nylas/fixtures/emails/email_15.html
Normal file
102
spec-nylas/fixtures/emails/email_15.html
Normal 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 <spang@nylas.com> 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> 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/<ns-id>/events?notify_participants=true -d '{ ... }'</div>
|
||||
<div>PUT /n/<ns-id>/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 <karim@nylas.com> 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> 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. <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 "{ title: 'test event', start: ... }"</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. </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. </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 </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>
|
12
spec-nylas/fixtures/emails/email_15_stripped.html
Normal file
12
spec-nylas/fixtures/emails/email_15_stripped.html
Normal 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>
|
13
spec-nylas/fixtures/emails/email_1_1.txt
Executable file
13
spec-nylas/fixtures/emails/email_1_1.txt
Executable 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
|
51
spec-nylas/fixtures/emails/email_1_2.txt
Executable file
51
spec-nylas/fixtures/emails/email_1_2.txt
Executable 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, you’ll 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
|
55
spec-nylas/fixtures/emails/email_1_3.txt
Executable file
55
spec-nylas/fixtures/emails/email_1_3.txt
Executable 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, you’ll 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
|
5
spec-nylas/fixtures/emails/email_1_4.txt
Executable file
5
spec-nylas/fixtures/emails/email_1_4.txt
Executable 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.
|
15
spec-nylas/fixtures/emails/email_1_5.txt
Executable file
15
spec-nylas/fixtures/emails/email_1_5.txt
Executable 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.
|
15
spec-nylas/fixtures/emails/email_1_6.txt
Executable file
15
spec-nylas/fixtures/emails/email_1_6.txt
Executable 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
|
55
spec-nylas/fixtures/emails/email_1_7.txt
Normal file
55
spec-nylas/fixtures/emails/email_1_7.txt
Normal 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, you’ll 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
|
37
spec-nylas/fixtures/emails/email_1_8.txt
Normal file
37
spec-nylas/fixtures/emails/email_1_8.txt
Normal 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
|
23
spec-nylas/fixtures/emails/email_1_stripped.html
Normal file
23
spec-nylas/fixtures/emails/email_1_stripped.html
Normal 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 <</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;">></span></pre>
|
||||
</span></div>
|
||||
|
||||
|
||||
</body>
|
148
spec-nylas/fixtures/emails/email_2.html
Normal file
148
spec-nylas/fixtures/emails/email_2.html
Normal 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 "/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
|
||||
<FolderSyncEngine at 0x5e4e550> 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>. 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 <menno@freshfoo.com><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> </div>
|
||||
</div>
|
||||
<div>
|
||||
<div>Hi Michael,<br>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>No problems about the delay. I know what it's like.<br>
|
||||
</div>
|
||||
<div> </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> </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> </div>
|
||||
<div>- Menno<br>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>p.s. Can you give me details on the bug that you found today?<br>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div> </div>
|
||||
<div>On Wed, 27 May 2015, at 06:59, Michael Grinich wrote:<br>
|
||||
</div>
|
||||
<blockquote type="cite">
|
||||
<div>Hi Menno,<br>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div>Sorry for the delay. <br>
|
||||
</div>
|
||||
<div> </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> </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> </div>
|
||||
<div>--Michael<br>
|
||||
</div>
|
||||
<div> </div>
|
||||
<div> </div>
|
||||
<div> </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 <menno@freshfoo.com> 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> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
25
spec-nylas/fixtures/emails/email_2_1.txt
Executable file
25
spec-nylas/fixtures/emails/email_2_1.txt
Executable 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.
|
||||
|
68
spec-nylas/fixtures/emails/email_2_stripped.html
Normal file
68
spec-nylas/fixtures/emails/email_2_stripped.html
Normal 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
|
||||
<FolderSyncEngine at 0x5e4e550> 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>. I don't think it's yet on PyPI.<br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
47
spec-nylas/fixtures/emails/email_3.html
Normal file
47
spec-nylas/fixtures/emails/email_3.html
Normal 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@ <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>hope you feel better! <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> </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>
|
20
spec-nylas/fixtures/emails/email_3_stripped.html
Normal file
20
spec-nylas/fixtures/emails/email_3_stripped.html
Normal 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@ <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>hope you feel better! <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
137
spec-nylas/fixtures/emails/email_4.html
Normal file
137
spec-nylas/fixtures/emails/email_4.html
Normal 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 <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. <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>Thanks,<br>
|
||||
</p>
|
||||
<p>Makala <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> </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. </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 </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"> 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. </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 <a href=3D"http://www.onemedic=
|
||||
al.com/enterprise/" tabindex=3D"-1" disabled=3D"true">One Medical Group.&nb=
|
||||
sp;</a> 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. </a> 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. =
|
||||
;</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 will also cover spouses and children=
|
||||
. </span></div>
|
||||
<div><br>
|
||||
</div>
|
||||
<div>As always, feel free to reach out with questions! </div>
|
||||
<div><br>
|
||||
</div>
|
||||
<div>Makala </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>
|
40
spec-nylas/fixtures/emails/email_4_stripped.html
Normal file
40
spec-nylas/fixtures/emails/email_4_stripped.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<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 <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. <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>Thanks,<br>
|
||||
</p>
|
||||
<p>Makala <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
49
spec-nylas/fixtures/emails/email_5.html
Normal file
49
spec-nylas/fixtures/emails/email_5.html
Normal 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> </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"><<a
|
||||
href="mailto:jeff@esper.com"
|
||||
target="_blank">jeff@esper.com</a>></span><br>Date: Mon, Jun 22, 2015 at
|
||||
4:00 PM<br>Subject: Nylas bug reports<br>To: support <<a
|
||||
href="mailto:support@nylas.com"
|
||||
target="_blank">support@nylas.com</a>><br>Cc: Michael Grinich <<a
|
||||
href="mailto:mg@nilas.com" target="_blank">mg@nilas.com</a>>, Christine
|
||||
Spang <<a href="mailto:spang@nylas.com"
|
||||
target="_blank">spang@nylas.com</a>>, Kavya Joshi <<a
|
||||
href="mailto:kavya@nylas.com" target="_blank">kavya@nylas.com</a>>, Karim
|
||||
Hamidou <<a href="mailto:karim@nylas.com"
|
||||
target="_blank">karim@nylas.com</a>><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>
|
7
spec-nylas/fixtures/emails/email_5_stripped.html
Normal file
7
spec-nylas/fixtures/emails/email_5_stripped.html
Normal 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> </div></div></div></div>
|
||||
</body>
|
49
spec-nylas/fixtures/emails/email_6.html
Normal file
49
spec-nylas/fixtures/emails/email_6.html
Normal 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>
|
48
spec-nylas/fixtures/emails/email_6_stripped.html
Normal file
48
spec-nylas/fixtures/emails/email_6_stripped.html
Normal 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>
|
255
spec-nylas/fixtures/emails/email_7.html
Normal file
255
spec-nylas/fixtures/emails/email_7.html
Normal 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! <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>And congrats on YC! <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 <eob@csail.mit.edu><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> </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 <<a href="mailto:mg@nylas.com">mg@nylas.com</a>> 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> <<a href="mailto:edward.benson@gmail.com" target="_blank">edward.benson@gmail.com</a>> on behalf of Ted Benson <<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>><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> </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">
|
||||
<<a href="mailto:mg@nylas.com" target="_blank">mg@nylas.com</a>></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. <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>. </div>
|
||||
<div>
|
||||
<div><br>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<div class="gmail_quote">On Wed, Apr 22, 2015 at 4:21 PM -0700, "Ted Benson" <span dir="ltr">
|
||||
<<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>></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. </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. </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> </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">
|
||||
<<a href="mailto:mgrinich@gmail.com" target="_blank">mgrinich@gmail.com</a>></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 <<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>> 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">
|
||||
<<a href="mailto:eob@csail.mit.edu" target="_blank">eob@csail.mit.edu</a>></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. </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! </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>
|
||||
:: <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>
|
||||
:: <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>
|
||||
:: <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>
|
||||
:: <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>
|
20
spec-nylas/fixtures/emails/email_7_stripped.html
Normal file
20
spec-nylas/fixtures/emails/email_7_stripped.html
Normal 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! <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>And congrats on YC! <br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
58
spec-nylas/fixtures/emails/email_8.html
Normal file
58
spec-nylas/fixtures/emails/email_8.html
Normal 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? 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 & paper suggestions</font>
|
||||
<div> </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>
|
16
spec-nylas/fixtures/emails/email_8_stripped.html
Normal file
16
spec-nylas/fixtures/emails/email_8_stripped.html
Normal 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? https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf<br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
34
spec-nylas/fixtures/emails/email_9.html
Normal file
34
spec-nylas/fixtures/emails/email_9.html
Normal 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,
|
||||
<div><br>
|
||||
</div>
|
||||
<div>My apologies, please use the below referral code when taking the Insights evaluation: </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>: <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>
|
||||
</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>
|
31
spec-nylas/fixtures/emails/email_9_stripped.html
Normal file
31
spec-nylas/fixtures/emails/email_9_stripped.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
|
||||
</head>
|
||||
<body>
|
||||
<div dir="ltr">Hi Christine,
|
||||
<div><br>
|
||||
</div>
|
||||
<div>My apologies, please use the below referral code when taking the Insights evaluation: </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>: <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>
|
||||
</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>
|
3
spec-nylas/fixtures/emails/email_BlackBerry.txt
Executable file
3
spec-nylas/fixtures/emails/email_BlackBerry.txt
Executable file
|
@ -0,0 +1,3 @@
|
|||
Here is another email
|
||||
|
||||
Sent from my BlackBerry
|
22
spec-nylas/fixtures/emails/email_bullets.txt
Executable file
22
spec-nylas/fixtures/emails/email_bullets.txt
Executable 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*
|
3
spec-nylas/fixtures/emails/email_iPhone.txt
Executable file
3
spec-nylas/fixtures/emails/email_iPhone.txt
Executable file
|
@ -0,0 +1,3 @@
|
|||
Here is another email
|
||||
|
||||
Sent from my iPhone
|
|
@ -0,0 +1,3 @@
|
|||
Here is another email
|
||||
|
||||
Sent from my Verizon Wireless BlackBerry
|
3
spec-nylas/fixtures/emails/email_sent_from_my_not_signature.txt
Executable file
3
spec-nylas/fixtures/emails/email_sent_from_my_not_signature.txt
Executable file
|
@ -0,0 +1,3 @@
|
|||
Here is another email
|
||||
|
||||
Sent from my desk, is much easier then my mobile phone.
|
43
spec-nylas/quoted-html-parser-spec.coffee
Normal file
43
spec-nylas/quoted-html-parser-spec.coffee
Normal 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)
|
289
spec-nylas/quoted-plain-text-parser-spec.coffee
Normal file
289
spec-nylas/quoted-plain-text-parser-spec.coffee
Normal 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
|
@ -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 >
|
||||
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[ ]*(>|>)/, # Plaintext lines beginning with >
|
||||
/<[br|p][ ]*>[\n]?[ ]*[>|>]/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
|
||||
|
|
101
src/services/quoted-html-parser.coffee
Normal file
101
src/services/quoted-html-parser.coffee
Normal 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
|
190
src/services/quoted-plain-text-parser.coffee
Normal file
190
src/services/quoted-plain-text-parser.coffee
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue