diff --git a/exports/nylas-exports.coffee b/exports/nylas-exports.coffee index 1bf9c1b90..efd1ec2b9 100644 --- a/exports/nylas-exports.coffee +++ b/exports/nylas-exports.coffee @@ -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 diff --git a/internal_packages/composer/lib/composer-view.cjsx b/internal_packages/composer/lib/composer-view.cjsx index 108a1dec3..feb74fae0 100644 --- a/internal_packages/composer/lib/composer-view.cjsx +++ b/internal_packages/composer/lib/composer-view.cjsx @@ -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 = (/^(]*>)+$/gi).test(body) + onlyHasDoc = (/^<\/head><\/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) diff --git a/internal_packages/composer/lib/contenteditable-component.cjsx b/internal_packages/composer/lib/contenteditable-component.cjsx index b6820815f..10b6ba4db 100644 --- a/internal_packages/composer/lib/contenteditable-component.cjsx +++ b/internal_packages/composer/lib/contenteditable-component.cjsx @@ -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(/()?()?<[^>]*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 diff --git a/internal_packages/composer/spec/composer-view-spec.cjsx b/internal_packages/composer/spec/composer-view-spec.cjsx index 96b3baae0..25e920d1b 100644 --- a/internal_packages/composer/spec/composer-view-spec.cjsx +++ b/internal_packages/composer/spec/composer-view-spec.cjsx @@ -329,7 +329,7 @@ describe "populated composer", -> useDraft.call @, to: [u1] subject: "Hello World" - body: "

This is my quoted text!
" + 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
sup
") + it "warns", -> warn.call(@, "Hey attach me
sup
") 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
attach
") + it "doesn't warn", -> noWarn.call(@, "Hey there
attach
") it "doesn't show a warning if you've attached a file", -> useDraft.call @, diff --git a/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx b/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx index c4b76e9e4..2456916e4 100644 --- a/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx +++ b/internal_packages/composer/spec/contenteditable-quoted-text-spec.cjsx @@ -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 NEW 1 HTML' + @changedHtmlWithoutQuote = 'Changed NEW 1 HTML

' @changedHtmlWithQuote = 'Changed NEW 1 HTML

QUOTE
' @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! NEW 1 HTML HTML HTML
' + changed = 'Hallooo! NEW 1 HTML HTML HTML
' @component.setState(showQuotedText: true) @performEdit(changed, @component) ev = @onChange.mostRecentCall.args[0] diff --git a/internal_packages/message-list/lib/email-frame.cjsx b/internal_packages/message-list/lib/email-frame.cjsx index f814ceb67..a6b3afed6 100644 --- a/internal_packages/message-list/lib/email-frame.cjsx +++ b/internal_packages/message-list/lib/email-frame.cjsx @@ -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 diff --git a/internal_packages/message-list/lib/message-controls.cjsx b/internal_packages/message-list/lib/message-controls.cjsx index 95ce819b4..0230ee5c2 100644 --- a/internal_packages/message-list/lib/message-controls.cjsx +++ b/internal_packages/message-list/lib/message-controls.cjsx @@ -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 # 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
}) @@ -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') diff --git a/spec-nylas/fixtures/emails/correct_sig.txt b/spec-nylas/fixtures/emails/correct_sig.txt new file mode 100755 index 000000000..4bb8a2a76 --- /dev/null +++ b/spec-nylas/fixtures/emails/correct_sig.txt @@ -0,0 +1,4 @@ +this is an email with a correct -- signature. + +-- +rick diff --git a/spec-nylas/fixtures/emails/email_1.html b/spec-nylas/fixtures/emails/email_1.html new file mode 100644 index 000000000..6791591af --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1.html @@ -0,0 +1,1730 @@ + + + + + +
+
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,
+

+Karim
+
From:  Kavya Joshi <kavya@nylas.com>
+
+ +
+Date: jeudi 28 mai 2015 20:21
+To: Jeff Meister <jeff@esper.com>
+Cc: Jennie Lees <jennie@nylas.com>, Andrew Lee <andrew@esper.com>, Mackenzie Dallas <mackenzie@esper.com>, + support <support@nylas.com>, Karim Hamidou <karim@nylas.com>, Christine Spang <spang@nylas.com>
+Subject: Re: Esper <-> Nilas
+
+

+
+
+
+
+

Hi Jeff,
+

+


+

+

The events are incorrectly marked as read only because of a bug in how we determine the organizer of an event; read-writable permissions are only granted to the organizer as per the Exchange ActiveSync protocol. Karim's working + on fixing the bug, it will be rolled out shortly and we'll keep you posted.

+


+

+

With respect to the rrule returned by the API - absolutely; we will change the representation and let you know when that's done too.

+


+

+

With respect to your question about when the timezone would be null for calendars - we're looking into it and will get back to you.

+


+

+

Thanks!

+

Kavya

+


+

+


+

+
+
+
From: Jeff Meister <jeff@esper.com>
+Sent: Wednesday, May 27, 2015 3:30 PM
+To: Kavya Joshi
+Cc: Jennie Lees; Andrew Lee; Mackenzie Dallas; support; Karim Hamidou; Christine Spang
+Subject: Re: Esper <-> Nilas
+
 
+
+
+
+
+
+
+
Hi Kavya,
+
+
+I just did some more testing with the Meg account, and everything related to recurring events worked correctly for me. I was about to try with one of our real Formation 8 accounts, but I ran into an issue syncing data back to O365 through Nylas: even non-recurring + events that I create or modify on the O365 side become read only in Nylas, so I'm unable to update them through your API. I remember we had this problem before and you guys fixed it, so maybe this was reintroduced during your recurring event changes, since + those events should be read only? Hopefully this isn't a complicated fix, let me know if I can provide more info.
+
+
+
The stuff below is not as important, just wanted to mention it:
+
+

+
+I noticed a couple things about the recurrence data in Nylas. First, the rrule is given as a single-quote-delimited string array packed inside a JSON string. We're currently dealing with this by taking the rrule string, replacing ' with ", then parsing the + now-valid JSON array. This could fail if there are other quote characters in the rule... could your API return the rrule simply as a JSON array containing double-quoted JSON strings, or would that break existing things? Second, I see that a recurrence entry + comes with a timezone, which is great because Google needs one, but my Meg calendars are always showing null for the timezone. Christine explained in the past that there are difficulties in getting timezones for Exchange calendars... do you know in what cases + this field will be non-null? For now, we require our users to specify their calendar timezone during onboarding, and we just use that zone every time.
+
+
+Thanks again,
+
+Jeff
+
+

+
On Wed, May 27, 2015 at 2:03 PM, Kavya Joshi +<kavya@nylas.com> wrote:
+
+
+
+

Hi Jeff,
+

+


+

+

We ran the script to back-fix recurring events created in the past (before last Friday) for the following accounts:
+

+


+

+

meg@espertech.onmicrosoft.com
+

+

stewiegriffin@espertech.onmicrosoft.com
+

+


+

+

Please note that as a result, the event and calendar IDs returned by the API will be different for these accounts.
+

+


+

+

Let us know if any of the recurring events look incorrect, thanks!

+

Kavya
+

+


+

+


+

+
+
+
From: Christine Spang
+Sent: Tuesday, May 26, 2015 4:32 PM
+To: Jeff Meister
+Cc: Kavya Joshi; Jennie Lees; Andrew Lee; Mackenzie Dallas; support; Karim Hamidou +
+

+Subject: Re: Esper <-> Nilas
+
+
+
 
+
+
+
+
Jeff, that's correct—read-only support for now.
+
+
+On May 26 2015, at 4:16 pm, Jeff Meister <jeff@esper.com> wrote: +
+
+
+
+
+
Hi Karim,
+
+
+I just ran my tests from the previous email again, and it looks like they're all working correctly now! I see the overrides working too; I tried deleting a single instance of an event and got an EXDATE as expected. I'll hook things up to GCal on our end and + let you know if I see any more recurrence issues.
+
+
+BTW, this is read-only support, correct? We shouldn't be attempting to make changes to recurring events through Nylas? (That's OK if so, just want to check and be sure.)
+
+
+Thanks,
+
+Jeff
+
+

+
On Tue, May 26, 2015 at 12:49 PM, Karim Hamidou +<karim@nylas.com> wrote:
+
+
+
Hi Andrew, Jeff
+

+
+
Sorry for the delay in getting back to you. We just deployed the changes to production and the code now supports all documented Exchange recurrence rules and event overrides.
+We will be running a script today to back-fix recurring events created before last Friday too.
+

+
+
We’ve tested the code extensively but as always, do let us know if you run into problems.
+

+
+
Karim       
+

+
+ +
+From: Andrew Lee <andrew@esper.com>
+Date: mardi 26 mai 2015 20:24 +
+

+To: Karim Hamidou <karim@nylas.com>, Christine Spang <spang@nylas.com>, Jeff Meister <jeff@esper.com>
+Cc: Kavya Joshi <kavya@nylas.com>, support <support@nylas.com>, Mackenzie Dallas <mackenzie@esper.com>, + Jennie Lees <jennie@nylas.com>
+Subject: Re: Esper <-> Nilas
+
+
+
+
+
+

+
+
+
+
Hey Karim, just checking on this. Did you guys release to production?
+
+
+
On Tue, May 19, 2015 at 11:59 AM Karim Hamidou <karim@nylas.com> wrote:
+
+
+
+
Hi Andrew + Esper team,
+

+
+
Just a quick heads up: we're currently testing the fixes on our staging system. We're planning to ship them to production by the end of the week.
+

+
+
regards,
+

+
+
Karim
+
+

+
+ +
+From: Andrew Lee <andrew@esper.com>
+Date: lundi 18 mai 2015 19:20
+To: Karim Hamidou <karim@nylas.com>, Christine Spang <spang@nylas.com>, Jeff Meister <jeff@esper.com>
+Cc: Kavya Joshi <kavya@nylas.com>, support <support@nylas.com>, Mackenzie Dallas <mackenzie@esper.com>, + Jennie Lees <jennie@nylas.com>
+
+
+ +
+
+Subject: Re: Esper <-> Nilas
+
+
+
+ +

+
+
+
+
Thanks for the e-mail Karim! Just let us know when you guys ship to production. -A
+
+
+
On Wed, May 13, 2015 at 12:08 PM Karim Hamidou <karim@nylas.com> wrote:
+
+
+
+
Hi Andrew,
+

+
+
Sorry for the delay in getting back to you. I was actually drafting a reply to Jeff. 
+
We’ve taken an iterative approach to this, and the recurring event code we shipped to production only supported the basic cases. That’s why Jeff found so many bugs -- sorry for not making this clearer.
+

+
+
However, I’ve been working on an updated version of the code which should support almost every recurrence rule. I also went through all the bugs you reported to make sure the new code fixes them.
+
It’s currently under review, so it should be shipped to prod early next week.
+

+
+

+
+
Sorry for the misunderstanding,
+

+
+
regards
+

+
+
ps @Jeff  —here’s a short list of what the new code supports:
+
- UNTIL and COUNT rules
+
- events recurring on the nth day of every month (e.g: a meeting occurring on the third wednesday of the month, of the year, etc.)
+
- complex recurrences (e.g: events occurring every three days but only on Wednesday and Fridays, etc.)
+

+
+
HOWEVER —we’re not yet supporting EXRULES. This is coming soon.
+

+
+
+

+
+ +
+From: Andrew Lee <andrew@esper.com>
+Date: mercredi 13 mai 2015 20:08
+To: Christine Spang <spang@nylas.com>, Jeff Meister <jeff@esper.com>
+Cc: Kavya Joshi <kavya@nylas.com>, support <support@nylas.com>, Mackenzie Dallas <mackenzie@esper.com>, + Karim Hamidou <karim@nylas.com>, Jennie Lees <jennie@nylas.com>
+
+
+ +
+
+Subject: Re: Esper <-> Nilas
+
+
+
+ +

+
+
+
+
Hey guys, just checking in and want to make sure we're giving everything you need. Let us know what the status is! 
+
+
+
On Fri, May 8, 2015 at 10:16 AM Christine Spang <spang@nylas.com> wrote:
+
+
+
+

Thanks for the info, Jeff! Karim's out today, but we'll take a look on Monday.
+

+
+
+
From: Jeff Meister <jeff@esper.com>
+Sent: Thursday, May 7, 2015 9:03 PM
+To: Christine Spang
+Cc: Andrew Lee; Kavya Joshi; support; Mackenzie Dallas; Karim Hamidou; Jennie Lees
+
+
+
+
+
+
+

+Subject: Re: Esper <-> Nilas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hi Christine,
+
+
+I got delayed working on other things, but I finally found time to test this today. I went back to my O365 test accounts, meg@ and +stewiegriffin@espertech.onmicrosoft.com, so I could mess around with the calendars without affecting customers. I started with Meg and tested some cases successfully, creating events + with the following recurrence patterns through the Office 365 Web interface:
+
+
+Pattern: Every day, no end
+
+Rule from Nylas: "['RRULE:FREQ=DAILY']"
+
+OK, as expected
+
+
+Pattern: Every week, on the same day as the first occurrence, no end
+
+Rule from Nylas: "['RRULE:FREQ=WEEKLY']"
+
+OK, as expected
+
+
+Pattern: Every other week, on the same day as the first occurrence, no end
+
+Rule from Nylas: "['RRULE:FREQ=WEEKLY;INTERVAL=2']"
+
+OK, as expected
+
+
+Those were all good. Next, I tried some more complicated recurrences:
+
+
+Pattern: Every week on Wednesday and Friday, no end
+
+Rule from Nylas: "['RRULE:FREQ=WEEKLY']"
+
+Expected BYDAY=TU,TH in RRULE
+
+
+
Pattern: Every day for 4 days
+
+
Rule from Nylas: "['RRULE:FREQ=DAILY']"
+
+
Expected COUNT=4 or UNTIL in RRULE
+
+

+
+Those didn't give the rules I expected based on my reading of RFC 2445, but they're pretty close, so hopefully they are not so difficult to fix. I'm more worried about the next pattern I tried:
+
+
+Pattern: Second Wednesday of every month, no end
+
+Rule from Nylas: None, event never synced
+
+Expected RRULE:FREQ=MONTHLY;BYDAY=2WE
+
+
+In this case, the event never showed up in my Nylas delta sync. Additionally, no events on that calendar have shown up in the delta sync since then, including the ones that worked fine before (even non-recurring events).
+
+I wasn't sure about the source of the problem, so I switched to my other testing account, Stewie, which has three calendars. I was able to reproduce the same pattern: everything works fine, even some basic recurring events, but when I try the every-second-Wednesday + pattern, it stops sending me data for that calendar. I repeated the pattern for Stewie's second calendar and got the same result. Finally, on his third calendar, I tried a slight variation (every third Thursday) with the same result.
+
+Since this seems consistent to me, I suspect I've run into a corner case in the recurrence code. Maybe you can see more in your logs? Let me know if I can provide any further info to help debug this.
+
+
+Thank you,
+
+Jeff
+
+

+
On Thu, May 7, 2015 at 3:11 PM, Christine Spang +<spang@nylas.com> wrote:
+
+
+
+

Hi Andrew & co,
+

+


+

+

Just checking in here. Were you able to successfully test the new recurring event support for Exchange?
+

+


+

+

regards,
+

+

Christine

+
+
+
From: Andrew Lee <andrew@esper.com>
+Sent: Sunday, May 3, 2015 5:05 PM
+To: Kavya Joshi; Jeff Meister
+Cc: support; Mackenzie Dallas; Karim Hamidou; Jennie Lees +
+

+Subject: Re: Esper <-> Nilas
+
+
+
 
+
+
+
+
+
Thanks so much for working round the clock on this Kavya + Nylas team! This is a very big milestone for us and we're really excited to finally put this all into action. We'll get back to you as soon as possible. 
+
+
+
On Fri, May 1, 2015 at 11:45 AM Kavya Joshi <kavya@nylas.com> wrote:
+
+
+
+

+

Hi Jeff,

+


+

+

We just shipped basic support for recurring events for Exchange.

+


+

+

Specifically, we now sync recurring events from the Exchange server and the API returns these events with an additional "recurrence" object that contains the RRULE and EXDATE strings.

+


+

+

For example, a recurring event would be returned as:

+

{

+

       …

+

       "recurrence": {

+

           "rrule": "['RRULE:FREQ=WEEKLY', 'EXDATE:20150611T210000Z,20150618T210000Z']",

+

           "timezone": null

+

       },

+

       "when": {

+

           "end_time": 1431034200,

+

           "object": "timespan",

+

           "start_time": 1431032400

+

       }

+

}

+


+

+

You can also expand recurring events using:

+

GET +https://api.nylas.com/n/<namespace_id>/events?expand_recurring=true

+


+

+

We don't support updating recurring events via the API yet, we'll be working on it in next week, but we wanted to get this out to you so you can get started!

+


+

+

As always, let us know if you have any questions!
+

+


+

+

Cheers,

+

Kavya
+

+


+

+


+

+
+
+
From: Jeff Meister <jeff@esper.com>
+Sent: Tuesday, April 28, 2015 3:34 PM
+To: Jennie Lees
+Cc: Andrew Lee; support; Mackenzie Dallas; Karim Hamidou
+
+
+
+
+
+
+

+Subject: Re: Esper <-> Nilas
+
+
+
+
+
+
+
+
+
+
+
Hi Jennie,
+
+
+Thank you for the update, it all sounds good to me! Having the recurrence info in standard RRULE format would be best for us, since that's what Google Calendar supports too. It would allow us to simply copy that data over to a Google event (unless you know + of recurrence features that we may get from Exchange events through Nylas that Google won't support). We may use the expansion feature as well, but I think an RRULE is what we'll want in most cases.
+
+
+Thanks,
+
+Jeff
+
+

+
On Tue, Apr 28, 2015 at 11:00 AM, Jennie Lees +<jennie@nylas.com> wrote:
+
+
+
+


+

+Hey Jeff, +

+
+
I wanted to give you a quick update on recurring events for Exchange while Christine's out. 
+

+
+
We're implementing them using the same approach we took for Google Calendar, including converting the Exchange recurrence info into a standard RRULE, which will be accessible on the event object via our API. However, we've found some nuances in the way + Exchange treats exceptions to the rule, so we're currently testing that thoroughly and making sure it lines up with our overall handling of events. You'll be able to access all individual instances of a recurring event without having to do the expansion yourselves, + and/or get the recurrence info from the parent.
+

+
+
If this doesn't sound like it's going in the right direction for you, let me know!
+

+
+
Jennie
+
+
+
+
From: Christine Spang
+Sent: Friday, April 24, 2015 3:52 PM +
+

+To: Jeff Meister
+Cc: Andrew Lee; Kavya Joshi; support; Mackenzie Dallas
+Subject: Re: Esper <-> Nilas
+
+
+
 
+
+
+
+
+
+

Good to hear things are working for you! :)
+

+


+

+

The "cancelled events" change was indeed intentional—we want the behaviour to match. This was part of some cleanups and bugfixing we did surrounding cancelled events.
+

+


+

+

We're going to work on implementing read-only Exchange support for recurring events next week. I think your use case is pretty straightforward, but we'll let you know if any questions come up that you folks could help us out with.
+

+


+

+

I'll be out of town next week but as long as you keep support@ in the loop someone from our side will keep you posted.
+

+


+

+

cheers,
+

+

Christine
+

+


+

+
+
+
From: Jeff Meister <jeff@esper.com>
+Sent: Friday, April 24, 2015 3:30 PM
+To: Christine Spang
+Cc: Andrew Lee; Kavya Joshi; support; Mackenzie Dallas
+Subject: Re: Esper <-> Nilas
+
 
+
+
+
+
+
+
+
+
Hi Christine,
+
+
+I reset the account on our end too and started the Nylas syncing again. Everything looks good so far! I haven't seen the duplicate issue today. Since it happened unpredictably before, I'll continue testing with this customer's account as well as others, just + to make sure it doesn't come back.
+
+
+I noticed a difference in my Nylas delta that isn't a bug but I thought was worth pointing out. When I delete an event on the O365 calendar, I used to see a delete in the Nylas delta. But in today's testing, instead of deletes, I saw updates with the status + of the event set to "cancelled". This is actually the same behavior as in Google's calendar API, and it's not a problem for us, just wanted to let you know about it and see if it was an intentional change on your end (or maybe O365 changed what they send you).
+
+
+Let me know when you have some thoughts on recurring events. I'm happy to discuss our use case over the phone if that's better for you.
+
+
+Thanks again,
+
+Jeff
+
+

+
On Thu, Apr 23, 2015 at 2:38 PM, Christine Spang +<spang@nylas.com> wrote:
+
+
+
+

Hi again,
+

+


+

+

We just shipped this fix to production and reset the affected account. This particular bug only occurs when the Exchange server encounters a particular condition, which is why it was both different from the earlier issue and doesn't affect all accounts.
+

+


+

+

Can you confirm that events look good for this account now?
+

+


+

+

Hold tight on recurring events for Exchange. We're still figuring out our gameplan there.
+

+

--Christine
+

+
+
+
From: Christine Spang
+Sent: Wednesday, April 22, 2015 4:17 PM
+To: Jeff Meister +
+

+Cc: Andrew Lee; Kavya Joshi; support; Mackenzie Dallas
+Subject: Re: Esper <-> Nilas
+
+
+
 
+
+
+
+
+
+

Hi Jeff,
+

+


+

+

Sorry for the delay here.
+

+


+

+

We have a fix for this duplicate events issue on our staging environment right now. We're planning to ship it to production tomorrow morning. Will let you know when this is done.
+

+


+

+

We're figuring out the best gameplan for Exchange recurring events. I'll let you know when we have more details.
+

+

--Christine
+

+
+
+
From: Jeff Meister <jeff@esper.com>
+Sent: Monday, April 20, 2015 6:19 PM
+To: Christine Spang
+Cc: Andrew Lee; Kavya Joshi; support; Mackenzie Dallas
+Subject: Re: Esper <-> Nilas
+
 
+
+
+
+
+
Hi Christine,
+
+
+
Glad you guys are back, hope you had a good retreat!
+
+

+Regarding recurring events, we can try expanding but I'm not sure if that will work in our case, unless I can make some changes to avoid us syncing these expanded copies back to the Exchange calendar. What would be better is if we could see the recurrence rules + on Exchange events. I think we could deal with that being read-only to start with, we just need an RRULE (or something we can translate into one) for our mirrored calendar on Google. I wrote some more details in my April 6 reply to this thread, explaining + what we're doing here. Let me know if you have other questions about this.
+
+
+Thanks,
+
+Jeff
+
+

+
On Mon, Apr 20, 2015 at 5:48 PM, Christine Spang +<spang@nylas.com> wrote:
+
+
+
+

Hi Andrew,
+

+


+

+

We just got back from retreat today and looking into this was on hold last week while we did some longer-term planning.
+

+


+

+

Karim is going to dive back into resolving the duplicate events issue now. We have a hunch as to what is up and will keep you posted.
+

+


+

+

To be clear, the recurring events issue is that recurring events expansion isn't supported on Exchange yet?
+

+


+Thanks a ton for your patience.
+

+

--Christine
+

+
+
+
From: Andrew Lee <andrew@esper.com>
+Sent: Monday, April 20, 2015 5:29 PM
+To: Kavya Joshi; support
+Cc: Jeff Meister; Mackenzie Dallas; Christine Spang
+Subject: Re: Esper <-> Nilas
+
 
+
+
+
+
+
Just realized that this isn't exactly the last issue, it's this duplicate events issue and the recurring events issue. But once we have those two - we have users chomping at the bit! +

+
+
Please let us know if there is anything we can help with. -Andrew
+
+
On Mon, Apr 20, 2015 at 9:39 AM Andrew Lee <andrew@esper.com> wrote:
+
+
Hey Kavya, any update on this. This is the LAST issue for us and we're really excited to do a launch so please let us know where we are here.
+
+
+
On Tue, Apr 14, 2015 at 9:35 PM Kavya Joshi <kavya@nilas.com> wrote:
+
+
Hi Jeff, Andrew +

+
+
We've identified the cause of the duplicate events - the sync engine uses 
+
Exchange server-assigned identifiers to identify events that were previously synced versus new events that need to be synced.
+
In the case of the duplicated events, we received the same event with a different server-assigned ID. As a result, the sync engine created a new event object with a different Nilas API event ID.
+
+

We're currently looking into what causes the Exchange server to return different IDs for the same event so we can implement a fix - we'll keep you posted.

+
+
Thanks!
+
+
+
Kavya
+

+
+
+

+
On Tue, Apr 14, 2015 at 11:30 AM, Andrew Lee +<andrew@esper.com> wrote:
+
+
Hope you guys had a great retreat! Just wanted to check up on the status of this. Happy to jump on a call too! -Andrew +
+

+
+
On Tue, Apr 7, 2015 at 4:44 PM Jeff Meister <jeff@esper.com> wrote:
+
+
+
+
Hi again,
+
+
+In testing today with a real customer's calendar, I ran into the duplicate events issue again. Not all events are affected, but a significant amount of them are. I'm not sure what, if anything, they all have in common... maybe they were modified at some point + after their creation on the Exchange side?
+
+
+
In more detail: I noticed this when doing an initial copy of events from Nilas for this user. At this stage, we aren't using the delta sync API; we start out by making a request for the next 10 weeks' worth of events on the chosen calendar. This request + returned 69 events, and I noticed that some were very similar, so I ran a little script that checked for pairs of events that are identical except for their event ID. It found 16 such pairs.
+
+
+
Since I hadn't yet hooked up the code to write changes on our end back to Nilas for this user, and I don't see any of these duplicates on his actual Exchange calendar, I suspect a Nilas issue... in the past you fixed a similar bug in the delta sync caused + by changing IDs in Exchange, maybe that's where the problem is?
+
+
+
The Nilas namespace ID of this user is 2ncnab6pkwjmckn6i2niknkr4
+
+
The calendar ID for all these events is d6hg8h7c57bx926toavj6tc9q
+
+
+
Here are the pairs of events that have identical data but different event IDs:
+5rc0a29m3j8aw39uln228uyv and enn1w0w1x862t39upeqnhde1e
+8e3axp1rtqon780xq3f2x242t and 6ov6kvsqswqrthru9ktnnvnlo
+fw39ttmbnqfttppvh9au9m35 and dvhdce880pbbpw5e0xmrp7iai
+d7pekrqot18ksbv76zjru0yzo and 2wqkb0ebzzzvmm5hl2dxuiymc
+2wqkb0ebzzzvmm5hl2dxuiymc and d7pekrqot18ksbv76zjru0yzo
+6jmiyxn5px913zw1havpfer7s and cft5si4ac6gsujouv87275qn0
+790x1c6omswe018o0om5uvtlh and 4apbs7rhfz7xjwkc5l0f2qxtn
+ev25fsxy2mm6wvdyn8ool0fs2 and 6f92dcpaeh4aheygi0px7e259
+c1ktorpee2wtjowfn9ushwtci and 8uy25k4r7ug6qwmn29upx06qf
+bf836ol0p8dbmq66pd342m3ij and co9aehxq3c8g1ugidfit76up4
+249ifo6pxjhdeqeqlknylfnkk and lcf6f4pjbhzrr11awkrjw6dn
+6rys9s4ag4sanho5lazglcjw9 and 9j7i0t664rkp5lcwofcs9g55m
+2u2sltush4ij0d4x502gz0zw8 and 6qw8hjuq2bxhxsv34w477p3r8
+47g1o8rpoejolplbqj9pyug9t and abpf9p2eeifrbksgy1432x82s
+87dks93wfcnsdoha8c6kpykf4 and 5bjnykg45fajv6x1f75wuvhay
+2oj9t3gz0wyz4ir4rjpm7ba2k and 67ruyrgk9iq9kiqshboh43j3h
+
+
+
Let me know if I can provide anything else that would help.
+
+
+
Thank you!
+
+
+
+
Jeff
+
+
+

+
On Mon, Apr 6, 2015 at 2:42 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
Hi Christine,
+
+
+I've been waiting for accounts to sync before using them with Esper due to the way we implemented things, which involves an initial sync of some calendar data and then uses your delta sync API thereafter. If we change it to use the delta for everything, and + Nilas sends us info as it comes in, that would probably work too. So I don't think this one is an issue on your end really, except that if I don't wait for the sync, there is significant delay before my actions on Nilas show up on the Exchange calendar. They + eventually do, so I figured the delay was due to the sync still running. But if I wait a while before using Nilas, then everything is fast.
+
+
+I spoke with our assistants, and it sounds like recurring events are pretty common on our customers' calendars. The reason I brought up Google, even though we're talking about Exchange calendars here, is because of how our assistant software works. Basically, + our assistants schedule in Gmail and Google Calendar, with our software providing extra capabilities inside those interfaces. To manage an executive's Google calendar, he can simply share it with us directly. But for Exchange, the executive logs in to Nilas + which syncs his calendar, then our backend fetches the data from Nilas and populates a corresponding Google calendar for the assistant to interact with. Then we watch the Google and Nilas calendars for new data to sync between them. So, if our assistant creates + a recurring event, the RRULE will be specified in Google's format, and we must translate it to a format that Nilas can handle for an Exchange calendar. That's why I was wondering about compatibility. We're taking your sync and doing another sync with it!
+
+
+
I think it's rare for an assistant to need to create a recurring event, and we could do that directly on Exchange for the time being... the main issue is how to present existing recurring Exchange events on Google. Without the recurrence info, we'd only + get a single event on our Google calendar, and the assistant wouldn't know that some open time on the calendar is actually taken up by the recurrences. I see in your API docs that there is an option to expand recurring events, which might work, but if our + assistant edits a recurring event, we will make an improper change to the Exchange calendar, probably creating a duplicate. (Our system assumes that anything on the Google calendar belongs on the corresponding Exchange calendar, and vice versa, so it will + try to propagate the expanded event back to Nilas, although perhaps we can special case this.)
+
+
+
Let me know how long you think it would take to get recurrence support for Exchange, if possible in the RRULE format that Google and others use, or something translatable to it. If it helps, I think we'd be OK with read-only support for a while, but without + that I think this may be a blocker for us, unless I can figure something out with expanded events.
+
+
+
As for timezone to fix all-day events, we do collect the timezone of our customers already, so we can supply that. For specifying the timezone, we use the IANA zone names (like America/Los_Angeles) as in the tz database. Users prefer the abbreviations, + but we've found this easier for machines, since abbreviations can be ambiguous and they require knowing about daylight savings (PST vs. PDT).
+
+
+
Thanks again,
+
+
Jeff
+
+
+
+
+

+
On Thu, Apr 2, 2015 at 6:41 PM, Christine Spang +<spang@nilas.com> wrote:
+
+For (2), we're also considering making a best guess on the user's timezone based on the timezone of the last event on the user's calendar, or a couple previous events. It requires a bit more thought. Let me know if you have any thoughts. (In general, we'd like + to avoid complicating the API with provider-specific parameters that make it so you can't specify the same event the same way across providers.) +
+

+
+
+On Apr 2 2015, at 6:03 pm, Christine Spang <spang@nilas.com> wrote: +
+Hi Jeff, +

+
+
The state machine for our Exchange syncs isn't being interpreted correctly by our developer console dashboard—we've created a ticket in our bug tracker to make sure we fix this.
+

+
+
Do you need an account to be fully synced before the user can start using Esper? Were you seeing problems before a day or so had passed? We've designed our sync algorithm to be usable from the time an account is connected, even if all data is not yet synced, + so I'm curious to hear if you were running into specific issues.
+

+
+
Here are answers to your other questions:
+
1. We have complete read-only support on Google calendars, but not for Exchange yet. Exchange uses a different format from RRULEs, and we haven't implemented the expansion yet. This is exposed via the `recurrence` property on events, but we haven't done + much testing so we're not sure if it's useful to end-users in its current state. (If you take a look at it at all, let us know what you find out.)
+

+
+
Do you need this? If so, we can add it to our roadmap. We've planned to do it, but it hasn't been a blocker for anyone yet so we've been prioritizing other features.
+

+
+
2. As Kavya explained previously, this is a fundamental limitation of how Exchange implements calendars that can only be solved by manually specifying the user's timezone correctly when creating an all-day event. We can't reliably determine this from a + user's account, so to fix this properly you'd have to be able to send our API the user's timezone. (On Exchange, "all day" events are treated as a 24-hour timespan, so if we send the event in UTC, and the user's calendar is set to display in PST... bam, display + issue.)
+

+
+
If you can get this timezone information, would this API modification to how "when" blocks work for Exchange event creation solve your problem?
+

+
+
"when" : {
+
+
    "date": "2015-04-15",
+
    "timezone": "PST"
+},
+

+
+
(Still thinking about the best way to specify timezones, but you get the gist of it.)
+

+
+
If so, this is not too difficult to implement on our end and we can have it to you by next week sometime. Let us know.
+

+
+--Christine
+
+On Apr 2 2015, at 3:46 pm, Jeff Meister <jeff@esper.com> wrote: +
+
+
+
+
+
+
+
+
Hi everyone,
+
+
+We decided to try one more testing account and have an assistant play with it before adding the real customer. This account sees daily use, so it's a more realistic test case than my fake accounts. The initial sync to Nilas took a while, but it seems to have + stabilized now, and events are syncing in both directions... looking good! We're going to have our real customer sign into Nilas and give his account some time to sync up (a day or so?) before using it. I noticed on my Nilas dashboard that our latest test + account sometimes says "Sync Issue" or "Downloading", but eventually it goes back to "Running".
+
+
+A couple minor questions for you:
+
+
+1. One thing I forgot to test was recurring events. It looks like you and Google use the same strategy for specifying recurring events: just embed the RRULE as a string in RFC format. I'm not too familiar with this part of iCalendar, so I'll have to read some + documentation, but I'm wondering if you think it will work to just copy RRULEs between Google and Nilas. Are you aware of any incompatibilities in how this spec is implemented? I'll keep looking into it myself, just wanted to know if there are any gotchas + I should be prepared for here. Recurrences get pretty complicated.
+
+
+2. I tried specifying an all day event with just a single date field as Kavya explained, and Nilas accepted this, but I'm still seeing the event span two days in O365: my all day 4/10 event covers both 4/9 and 4/10. It's consistently going back one day for + me... maybe this is related to the timezone issue Kavya also mentioned? My Nilas API responses just have dates, but if 4/10 is being interpreted in PST, that might be subtracting some hours and getting us to 4/9. Not sure though.
+
+
+I'll let you know if we run into problems with our customer's account, which is rather large. I'm optimistic, though!
+
+
+Thanks,
+
+Jeff
+
+

+
On Tue, Mar 31, 2015 at 2:35 PM, Andrew Lee +<andrew@esper.com> wrote:
+
+
Yep. By the end of the week (barring crazy things happening), we should be testing with some current O365 executives and we'll let you know if we experience any problems. 
+
+
+

+
On Tue, Mar 31, 2015 at 2:07 PM Christine Spang <spang@nilas.com> wrote:
+
+
Per our conversation today: +

+
+
You folks are going to start onboarding Exchange users, having them directly auth to Nilas instead of using Exchange shared calendars.
+

+
+
We're going to scope+spec application scopes (specifically for calendars vs mail) and work implementation into our roadmap. We have a big conference+company offsite coming up in the next two weeks, so are not sure about the timing for the project right + now. In the meantime, you can get started without anything specific from us.
+

+
+
As mentioned on the call, we're right in the middle of an improvement project for our Exchange auth flow, which includes providing better error messages when things go wrong and creating some better documentation about what each screen of the auth flow + looks like on our end. Let us know if you have any questions before that's live, and if you have any trouble with Exchange sign-ins, we're available to help figure out what's going wrong.
+

+
+
cheers,
+
Christine
+
+

+
On Mon, Mar 30, 2015 at 6:00 PM, Christine Spang +<spang@nilas.com> wrote:
+
+

Great, talk to you tomorrow. :)

+
+
+
On Mar 30, 2015 5:48 PM, "Andrew Lee" <andrew@esper.com> wrote:
+
+
Yeah, that's fine. Let's jump on the phone then. Blake will follow up with calendar invites etc.
+
+
On Mon, Mar 30, 2015 at 5:46 PM Christine Spang <spang@nilas.com> wrote:
+
+
Does 1pm tomorrow work for a call?
+

+
On Mon, Mar 30, 2015 at 5:28 PM, Andrew Lee +<andrew@esper.com> wrote:
+
+
Thanks so much for the e-mail! Christin, on Shared Calendars, we're happy to jump on the phone today or tomorrow to come up with a solution. This is very important to us and we'd like to get a resolution soon. -A
+
+
+

+
On Mon, Mar 30, 2015 at 12:48 PM Kavya Joshi <kavya@nilas.com> wrote:
+
+
Hi Jeff, +

+
+
Thanks for the detailed response!
+

+
+
With respect to all-day event creation, there are a couple of issues:
+

+
+1. The display of all-day events depends on the timezone the calendar is set to in O365 (Calendar settings < Options < General < Region and timezone): + +

+
Since the `when` field for event creation using the Nilas API is specified in the UTC timezone and is sent to the Exchange server in this timezone too, if your O365 calendar is set to the UTC timezone, it will display as expected. However, if your calendar + is set to a different timezone (for e.g. UTC-8), the event will display incorrectly. The same behavior is observed if you create an all-day event in the O365 UI and then change the timezone in the options.
+
+

+
+
To overcome the timezone-dependency of the display, we plan to allow users to specify timezone information as well while creating events - we'll keep you updated on it.
+
+

+
+
2. There was a small error in the end-dates we were sending the server for all-day events, which caused the incorrect event durations. The fix for this is now live, so this should not be an issue anymore - please let us know if it is!
+

+
+

+
+
To answer your question about date ranges for events -
+
* to create an all-day event on a single day using the API, provide a date object for the `when` field. 
+
For example, to create an all-day event on 04/11/2015:
+
+
when = {
+
            "date": "2015-04-11"
+
}
+
+

+
+
* to create an all-day event spanning multiple days, provide a datespan object for the `when` field. In this case, the end-date is /inclusive/.
+
For example, to create an event that runs all-day on the 11th, 12th, 13th and 14th (inclusive): 
+
+
when = {
+
            "start_date": "2015-04-11",
+
            "end_date": "2015-04-14"
+
}
+
+

+
+
Does that help?
+

+
+
With respect to shared calendars, Christine will be in touch.
+

+
+
Thanks!
+
+
+
+
+
+
+
+
+
Kavya
+
+

+
+
+
+
+
+
+

+
On Thu, Mar 26, 2015 at 3:07 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
+
+
+
On the topic of shared calendars, let me explain our use case. Esper employs some in-house assistants to handle scheduling for our customers, who are mostly busy executives. Our assistants use custom software that is built on top of Gmail and Google Calendar, + both frontend (browser extension to enhance the interface) and backend (server to automate various workflows using Google APIs). Each assistant logs into a Google account, and our customers share their calendars from their own private Google account to ours. + This lets our assistants see free/busy times for the executives and create appropriate events for them, without requiring the executive to give us his private login information.
+
+
+We would like to support executives who use Exchange calendars, but we're trying to avoid replicating our frontend work on top of O365 or desktop Outlook, and we don't want to deal with the Exchange APIs either. Our plan was to have Exchange executives share + their calendars with our assistant's own Exchange account (often created by the executive's organization for our use). Then, we would authenticate our assistant's account with Nilas, and our backend would use your API instead of Microsoft's. For the frontend, + we have some custom code that mirrors the Nilas data over to a Google calendar where our assistant can use our software.
+
+
+I tried checking my test O365 account that had some shared calendars with my iPhone like you suggested, and indeed I did not see the shared calendars... only the owned calendars showed up. Unless there's an undocumented part of EAS that Apple isn't aware of, + I guess we may not be able to access shared calendars through that protocol, as you said. The shared calendars do show up on the O365 interface, and everything works there as you'd expect, but since that's a cloud service I have no idea what (likely private) + API they are using. If I can get another non-O365 Exchange account, I'll try some sharing tests there too. However, the customer we would like to onboard first does use O365.
+
+
+If we can't get shared calendars through EAS or another protocol, we may have to rethink how we're doing things. I thought of sharing the calendar in the other direction, where our assistant creates it on her account and shares it with the executive, but then + we still wouldn't be able to see free/busy times on the executive's other calendars, and they probably won't want to use our calendar exclusively. The only other thought I'm left with is to have the executive sign in to Nilas with his own account. But that + just pushes the privacy issue up the chain; while you could restrict our access through your API, our customers probably don't want a third party having access to all their email etc. Does Exchange have some access control capabilities that could help here? + Maybe there is a way for our customers to sign in to Nilas with their own accounts, but then choose only to share their calendars with Nilas, kind of like how Google's OAuth domains work?
+
+
+Sorry for the long email, I'm just trying to be clear. I should have noticed this issue before, but I had been testing with owned calendars figuring that shared calendars would work the same, as they do in Google. That's what I get for assuming EAS would be + cooperative! If you can think of an alternative approach, we'd be very happy to hear it.
+
+
+Thanks again, and let me know if you need any more info or clarification.
+
+- Jeff
+
+
+
+

+
On Thu, Mar 26, 2015 at 12:40 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
+
Hi Kavya and others,
+
+
+I just tested the all-day events again, and the problem of showing up at 4 or 5pm is gone, but I'm still having some issues that I think are related to whether the end date is exclusive or inclusive. When I request an all-day event through the Google Calendar + API, say on 4/2, I get back an event with a start date of 4/2 and end date of 4/3. I was assuming that Nilas worked the same way, with exclusive end times/dates (i.e., just beyond the end of the time range).
+
+
+When I create an event on Nilas with a start date of 4/2 and end date of 4/3, it appears on the O365 calendar across two days: 4/1 and 4/2. Opening its details confirms that it has those start and end dates, even though my Nilas API response said 4/2 to 4/3. + Strangely, the sidebar of O365 says "1 day" for the event duration. To make it appear only across 4/2, I had to go into the event on O365 and set both start and end dates to 4/2.
+
+
+So, I tried creating a Nilas event with start and end dates both set to 4/2. This did result in a single-day event in O365, but it was on 4/1 instead of 4/2. The sidebar also says "0 minutes", even though everything looks fine on the calendar. I noticed that + my Nilas API response contained a when field with a single date 4/2 instead of a datespan, so I also tried creating my event this way, but the result on O365 was the same as the 4/2 to 4/2 datespan.
+
+
+It seems like there is some confusion between me, Nilas, and O365 about whether an end time or date is part of the range. How should I be specifying my ranges to Nilas? And could this account for the off-by-one behavior I'm seeing on my O365 calendar?
+
+
+
+

+
+
I'll write a different response regarding shared calendars, because this email is already a bit long.
+
+
+
Thank you,
+
+
Jeff
+
+
+
+
+
+
+
+

+
On Wed, Mar 25, 2015 at 9:49 PM, Kavya Joshi +<kavya@nilas.com> wrote:
+
+
Hi Andrew, Jeff +

+
+
At first glance, it seems like the ActiveSync protocol does not support syncing shared calendars. We're going to double check this with a mobile phone, since sometimes ActiveSync features are undocumented. 
+
In the meantime, could you please tell us how you plan to use synced shared calendars? We don't want this to be a blocker for you and might be able to suggest an alternative approach.
+

+
+
W.r.t creating all-day events through Nilas, did you have the chance to test it again?
+
From the logs, it looks like your prior testing for the following accounts might've occurred before the fix was deployed:
+ + +

+
+
Thanks!
+ +
Kavya
+

+
+
+
+
+

+
On Wed, Mar 25, 2015 at 4:26 PM, Christine Spang +<spang@nilas.com> wrote:
+
+
Hi Andrew, +

+
+
Kavya's taking a look at this today. We'll get back as soon as we have more info.
+ +

+
+
--Christine
+
+
+
+

+
On Wed, Mar 25, 2015 at 9:35 AM, Andrew Lee +<andrew@esper.com> wrote:
+
+
Hey guys, just wanted to check up on what you think about this issue. I'm really hoping it's a small issue because once this is fixed, we're gonna start on-boarding people.
+
+
+

+
On Mon, Mar 23, 2015 at 5:01 PM Jeff Meister <jeff@esper.com> wrote:
+
+
+
+
+
Hello again,
+
+
+Andrew brought another issue to my attention: some of the calendars we use are not actually owned by the account we log into. They are owned by a different account and shared with us. On the O365 interface, these calendars appear under "Other calendars" instead + of "My calendars", similar to Google's behavior. I tried sharing a couple calendars between test accounts, and they show up in O365, but I haven't seen them appear in Nilas. I don't think it's a sync delay issue, because I created another owned calendar and + that one showed up in Nilas immediately. Are you folks able to access and sync shared O365 (or other Exchange) calendars? I'm hoping this is simply a matter of checking an extra data field.
+
+
+Thanks,
+
+Jeff
+
+

+
On Mon, Mar 23, 2015 at 2:53 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
+
Hi Christine,
+
+
+I was just about to send an email with our latest results. From my testing this morning, I see that deletes and guests are working, so the issues (1) and (2) from my previous email are fixed! Things are looking pretty good today, I'm seeing creates/updates/deletes + sync in both directions.
+
+One thing maybe worth mentioning: when I began testing this morning, at first I thought Nilas wasn't syncing to O365, because my actions weren't going all the way through to the O365 side even though my Nilas API calls were successful. However, when I checked + back around 30min later, everything had showed up. I presume you have to queue up actions to send to the real Exchange server, but usually my Nilas interactions sync back within seconds (except deletes sometimes take a bit longer). Perhaps your reset of my + test accounts made them require a bit more background syncing to catch up to the point where my actions would go through? Anyway, it's working more quickly now, I'm just curious what we should expect when dealing with real customer calendars (which will be + larger than my testing ones). Should we give a new customer's Nilas account some time to sync up with their Exchange server before trying to use it further?
+
+As for all-day events, the ones I created through O365 are showing up properly on the Nilas side, but I'm still seeing all-day events show up at improper times on my O365 calendar when I add them through Nilas. Maybe your deploy happened after I tested that, + so I'll keep trying this afternoon and let you know how it goes.
+
+
+Still no luck with Andrew's Exchange account, same error as before. I'm not sure what's up with it. If I can get another standalone Exchange account to test with, I'll report my findings there.
+
+
+Thank you,
+
+Jeff
+
+
+
+

+
On Mon, Mar 23, 2015 at 2:34 PM, Christine Spang +<spang@nilas.com> wrote:
+
+
Heya folks, +

+
+
We hadn't implemented support for the "All Day" bit for Exchange events. We just shipped a patch this morning that implements this support. Does this fix your issue?
+

+
+
Has anything else come up?
+

+
+
Let us know.
+ +
--Christine
+
+
+
+

+
On Fri, Mar 20, 2015 at 6:58 PM, Christine Spang +<spang@nilas.com> wrote:
+
+
+
Eben scrutinized our logs about the Exchange issue, and as far as we can tell, the Exchange server may have repeatedly encountered an internal server error trying to service the auth request. We haven't seen this before, but as best we can tell there's + not much we can do in this case, other than have you try again to auth later. Does it work for you now?
+

+
+
We'll keep in mind that this can happen as we're working on improving the feedback in the Exchange auth flow.
+

+
+
Karim's worked up a fix for all day events as well, which we'll ship to production as soon as it's not Friday night. :)
+

+
+
Have a great weekend,
+
Christine
+
+
+
+

+
On Fri, Mar 20, 2015 at 1:41 PM, Christine Spang +<spang@nilas.com> wrote:
+
+Hey Andrew, +

+
+
He sure did. Shipped with a regression test this time, and I reset the calendar syncs for your Exchange accounts so this should be back-applied to existing events as well.
+
+
+
Eben's going to trawl the logs to look at what happened with your attempt to reauth your college account today as well, and Karim reports that it's not too difficult to fix the issue with all day events and Exchange either, so hopefully we'll have that + fixed on production for you folks next week.
+

+
+
Excited that you can start testing with a customer! Let us know what we can do to support.
+ +
--Christine
+
+
+
+
+On Mar 20 2015, at 11:54 am, Andrew Lee <andrew@esper.com> wrote: +
+
Hi Christine,
+
+
Just wanted to check on whether Karim was able to come up with a fix for the second issue. We can begin testing if you guys have a sense as to what's happening there.
+

+
+
-Andrew
+
+
+
On Tue, Mar 17, 2015 at 2:06 PM Christine Spang <spang@nilas.com> wrote:
+
+
Hi Jeff, +

+
+
1. The deletes issue was due to a bad code deploy yesterday, which we have already fixed. Sounds like you were testing right around the time of the deploy!
+

+
+
2. Karim's going to take a look at this later today.
+

+
+
re: Andrew's school Exchange account, we'll take a look at the logs. We also have a project planned to make the Exchange sign-in process less arcane, which will make whatever is going wrong in cases like this more apparent to the user signing in.
+

+
+
I'll see if we can take a look at the all day events issue this week as well.
+
+
+

+
+
--Christine
+

+
+

+
+
+

+
On Tue, Mar 17, 2015 at 1:01 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
+
+
+
+
+
Hello Michael and other Nilas folks,
+
+
+I've done some testing of your latest fixes, and I'm happy to report that the sync anomalies from before are no longer occurring! With two Office 365 test accounts, Nilas has been syncing consistently both ways, although I did run into a few bugs, listed below. + These things were all working in the past, so hopefully they are not huge issues to fix. These two are the most important for us:
+
+
+1. Event deletions aren't propagating from Nilas to my O365 calendar anymore. The Nilas API call to delete the event succeeds (with a response of null), and I see my delete in the delta sync, but the event remains in O365. I waited a while to see if the deletion + was simply queued up, but it never went through.
+
+
+2. When I add a guest on my O365 event that only has an email address (no name), the Nilas event I get has the email address in the participant name field and the email field set to null. This didn't happen in the past; I remember that guests were working both + ways in previous tests. A guest added to Nilas shows up fine on the O365 side.
+
+
+
This one is less important, since the initial customer we'll be trying out is on O365:
+
+

+
+- I can't sign in with Andrew's school Exchange account anymore. Maybe I'm doing something wrong, but I think I used the same login procedure that worked last time... it just tells me "Sorry, an error occurred." Can you see anything in your logs for account +alee07@cmc.edu that might explain more?
+
+
+And finally, this one is not a regression, it's an issue I've seen before that I just wanted to bring to your attention again:
+
+- All-day events show up on my O365 calendars at the wrong time. Here is what I said before:
+"If I create an all-day event through Nilas, say on Thursday, then I get the expected API response with a datespan of 2015-02-12 to 2015-02-13, but the +event I see on O365 is actually from 2015-02-11 4:00pm to 2015-02-12 4:00pm. My calendar is in Pacific Time, if that helps. I've only seen this happen for +all-day events."
+
+I'm repeating this because now I'm noticing that the event is from 5pm to 5pm, and we're in DST now but weren't when I reported 4pm to 4pm before. So, I suspect the bug is related to timezone handling for all-day events.
+
+
+Let me know if you need any more details. Thanks again!
+
+Jeff
+
+
+
+

+
On Sun, Mar 15, 2015 at 9:30 PM, Michael Grinich +<mg@nilas.com> wrote:
+
+
Hi Jeff, +

+
+
Regarding the ActiveSync protocol, I usually invoke Hanlon's razor. :(
+

+
+
We *definitely* want you to have a big success with this first account! Did you find any bugs or issues this weekend? What can we do to help you roll-it out?
+

+
+
Exciting stuff! 
+ +

+
+
--Michael
+

+
+

+
+

+
+
+
+
+

+
On Fri, Mar 13, 2015 at 2:28 PM, Jeff Meister +<jeff@esper.com> wrote:
+
+
+
+
+
Hi Christine, So the Exchange server sometimes decides to just change event IDs on you, and you have to detect this situation by comparing the data fields? Wow, that almost sounds like Microsoft is breaking compatibility for third parties intentionally. + I'm really glad you were able to figure it out!
+
+
+I'll be doing some more testing this afternoon and over the weekend, so I'll let you know if I run into other issues, but I think this was the last major thing blocking us. A few weeks ago, I successfully demonstrated creation, modification, and deletion of + events being propagated both ways between Nilas and an O365 account. For whatever reason, the event ID changing didn't occur that time. And I suspect that my previous issues with occasional floods of sync data, which included duplicates of old events, were + due to the same ID-changing issue you just fixed.
+
+So, if everything looks fine with my test accounts over the weekend, I think our next step will be to try hooking up a real customer. We currently have an executive who we're serving separately with regular MS Exchange software, and our assistants would really + like to use our own software instead, which Nilas integration will enable. But I've been worried about the possibility of bugs either messing up the executive's live calendar or confusing the assistants on our end, which is why I've been using fake test accounts. + If the anomalies don't appear again, I'll feel more confident about hooking up this customer.
+
+
+Thank you,
+
+Jeff
+
+
+
+

+
On Thu, Mar 12, 2015 at 5:40 PM, Christine Spang <spang@nilas.com> wrote:
+
+Hi Andrew and Jeff, I wanted to let you know that we shipped this fix to production this week as expected. If you see any more anomalies with event changes in delta sync responses, let us know. It's still possible that if you change every single part of an + event (title, location, start time, end time), the ID could change in the API response---but modifications to a subset of attributes should retain the same ID. What are your next testing steps? I'm curious how far along you folks are, and what else may be + blocking you. --Christine On Fri, Mar 6, 2015 at 8:30 PM, Andrew Lee <andrew@esper.com> wrote: Thanks so much for the update Christine! On Fri, Mar 6, 2015 at 7:51 PM Christine Spang <spang@nilas.com> + wrote: Hi Andrew, We shipped this fix to our staging environment today, and expect to ship it to production next week. Basically, there are some cases in Exchange ActiveSync where event IDs provided by the server may change, and we needed to implement extra + heuristics to match events in order to prevent our event IDs from changing as well. As you can imagine, this is pretty tricky to get right, so we've been taking some extra time to test it. I'll let you know when this is live. --Christine On Mar 5, 2015 3:12 + PM, "Christine Spang" <spang@nilas.com> wrote: Hi Andrew, Apologies for the long delay here. Kavya has figured out what's going with the changing IDs for Exchange calendar, and has been working on a fix for a few days now. + We don't need any more information from you folks right now. You've been super helpful testing the calendar support. Thanks a ton. --Christine On Thu, Mar 5, 2015 at 1:27 PM, Andrew Lee <andrew@esper.com> wrote: Hey guys,  + Just wanted to check up on the status of your changes related to Nilas/Esper. I took the liberty of creating a separate e-mail thread, since the last one was 61 messages long, kept just the latest two messages that mattered, and removed Tikhon, nilas support, + and removed all of your old inboxapp e-mails. Let us know how we can help! -Andrew On Tue, Feb 24, 2015 at 11:18 AM, Christine Spang <spang@nilas.com> wrote: Thanks for this report, Jeff. We're looking into it and should + have some more info for you soon. On Mon, Feb 23, 2015 at 2:42 PM, Jeff Meister <jeff@esper.com> wrote: Hi Christine, I was able to sign in with Andrew's Exchange account the way you explained, so I tried some testing with + it today. Event creation and update was working fine in both directions, but then a sync issue popped up again, and I think it might have been triggered by me doing a delete (not sure though). Some old events appeared in my Nilas delta sync, including one + duplicate. I went in our logs to investigate, and by searching for the details of the duplicate event, I found that it had been identified in previous syncs by ID "1d2ex1lni3t89w3rxh8d0in9c"... but after a certain point, I stopped seeing that ID, and instead + saw ID "1zeaati82ubv706f0sraii380" with the same event details. This ID first appeared in the abnormal sync I received, and I've attached the JSON data that I saw, but here's a summary with (C)reate/(M)odify/(D)elete, ID, and title: D, 4oerzc51uazhwsnb52jbwlrgn + C, dd438y3gd0xelvukq3lj6vhax, Lunch time!!! M, ah3q993oiyo3rwtieot25e5v, Wednesday event M, 7nrro21ez0cjsopbcftl0aggo, Friday morning M, 4pqygmkpyylqqvjffqx6gwbec, Friday night M, 2x130rv0h6ujkphj4s5xwwduk, Call with some dude M, 1zeaati82ubv706f0sraii380, + Short meeting M, 11kzbfgr3bes717kgv1bjwc02, Look out the window Some of these events are from my testing today, but "Wednesday event", "Friday morning", and "Friday night" are from a week or two ago, and I haven't touched them since then. This is what I mean + by old events suddenly appearing. Our current implementation actually ignores the data in the delta sync besides the ID, then it goes and looks up the event details using the endpoints based on ID. Looking at the logs, that appears to have worked fine at the + time, and the existence of two IDs with the "Short meeting" details explains why I saw a duplicate (our system assumes that new ID = new event). Now, some minutes later, after I tried looking up the details of ID "1d2ex1lni3t89w3rxh8d0in9c" and got "Short + meeting" as expected. But when I tried looking up "1zeaati82ubv706f0sraii380", expecting the same "Short meeting" as I saw in the delta sync... instead I got a different event, "Wednesday event"! Confused, I went to look up the rest of the IDs above, and here's + what I got (ID, title): 4oerzc51uazhwsnb52jbwlrgn, N/A (event was deleted) dd438y3gd0xelvukq3lj6vhax, Look out the window ah3q993oiyo3rwtieot25e5v, Friday morning 7nrro21ez0cjsopbcftl0aggo, Friday night 4pqygmkpyylqqvjffqx6gwbec, Call with some dude 2x130rv0h6ujkphj4s5xwwduk, + Short meeting 1zeaati82ubv706f0sraii380, Wednesday event 11kzbfgr3bes717kgv1bjwc02, Look out the window It almost looks like an off-by-one pattern, but there are weird swaps too that don't seem to follow any rule. I have no idea what to make of this. I can't + find anything in our logs to suggest our side is causing this... the last appearance of e.g. "1zeaati82ubv706f0sraii380" in my log file associates it with "Short meeting", and I see no API calls that could have put "Wednesday event" data in there. I wish I + could reproduce this better and more consistently... hopefully this detail is of some use. I've attached a pretty-printed JSON file containing the sync whose IDs I'm referring to above. Thanks again, Jeff +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_10.html b/spec-nylas/fixtures/emails/email_10.html new file mode 100644 index 000000000..f360aabec --- /dev/null +++ b/spec-nylas/fixtures/emails/email_10.html @@ -0,0 +1,22 @@ + I saw it this weekend and will totally go +again. :) One of the best movies I've seen recently! 

On Jul 6 2015, at 3:58 pm, Makala Keys +<makala@nylas.com> wrote:
Hey Team,

+
Let's go see a movie! The latest Pixar movie, Inside Out, is supposed to +be excellent (link to the trailor below). 
I'm +taking a headcount for the AMC Van Ness 14 show on WED @ 7:45pm. +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!
+

Thanks, 
+Makala 


+
https://www.youtube.com/watch?v=seMwpP0yeu4
+

+
http://www.rottentomatoes.com/m/inside_out_2015/

+
diff --git a/spec-nylas/fixtures/emails/email_10_stripped.html b/spec-nylas/fixtures/emails/email_10_stripped.html new file mode 100644 index 000000000..33fee7af2 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_10_stripped.html @@ -0,0 +1,3 @@ + I saw it this weekend and will totally go +again. :) One of the best movies I've seen recently! 

+ \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_11.html b/spec-nylas/fixtures/emails/email_11.html new file mode 100644 index 000000000..4309128df --- /dev/null +++ b/spec-nylas/fixtures/emails/email_11.html @@ -0,0 +1 @@ +

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

diff --git a/spec-nylas/fixtures/emails/email_11_stripped.html b/spec-nylas/fixtures/emails/email_11_stripped.html new file mode 100644 index 000000000..160d786fd --- /dev/null +++ b/spec-nylas/fixtures/emails/email_11_stripped.html @@ -0,0 +1,2 @@ +

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

+ \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_12.html b/spec-nylas/fixtures/emails/email_12.html new file mode 100644 index 000000000..9cca5772c --- /dev/null +++ b/spec-nylas/fixtures/emails/email_12.html @@ -0,0 +1,70 @@ + +
+ 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 + diff --git a/spec-nylas/fixtures/emails/email_12_stripped.html b/spec-nylas/fixtures/emails/email_12_stripped.html new file mode 100644 index 000000000..e1717d859 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_12_stripped.html @@ -0,0 +1,49 @@ + +
+ Hi,
+ + +

+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." +

+ + +
+
+
+
+_______________________________________________ +riak-users mailing list +riak-users@lists.basho.com +http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com + +
\ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_13.html b/spec-nylas/fixtures/emails/email_13.html new file mode 100644 index 000000000..28c326021 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_13.html @@ -0,0 +1,56 @@ + +
+ 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 +> + +
+ diff --git a/spec-nylas/fixtures/emails/email_13_stripped.html b/spec-nylas/fixtures/emails/email_13_stripped.html new file mode 100644 index 000000000..5d1aed776 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_13_stripped.html @@ -0,0 +1,12 @@ + +
+ Oh thanks.
+ + Having the function would be great.
+ + -Abhishek Kona
+
+ + + + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_14.html b/spec-nylas/fixtures/emails/email_14.html new file mode 100644 index 000000000..6367c9594 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_14.html @@ -0,0 +1 @@ +

Recruiting Email Weekly Blastoff

Turn those cold leads into phone screens! You can make this go super fast by queueing up your drafts before hand and just sending them out during this time.
When
Fri Feb 27, 2015 5pm – 5:30pm Pacific Time
Calendar
Ben Gotow
Who
Michael Grinich - organizer
Kartik Talwar
team
Rob McQueen
Evan Morikawa
Christine Spang
Karim Hamidou
nylas.com@allusers.d.calendar.google.com
Makala Keys
Eben Freeman
Jennie Lees
Ben Gotow
Kavya Joshi

Going?    - -     

Invitation from Google Calendar

You are receiving this email at the account ben@nylas.com because you are subscribed for invitations on calendar Ben Gotow.

To stop receiving these emails, please log in to https://www.google.com/calendar/ and change your notification settings for this calendar.

diff --git a/spec-nylas/fixtures/emails/email_14_stripped.html b/spec-nylas/fixtures/emails/email_14_stripped.html new file mode 100644 index 000000000..d97c20fa7 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_14_stripped.html @@ -0,0 +1,2 @@ +

Recruiting Email Weekly Blastoff

Turn those cold leads into phone screens! You can make this go super fast by queueing up your drafts before hand and just sending them out during this time.
When
Fri Feb 27, 2015 5pm – 5:30pm Pacific Time
Calendar
Ben Gotow
Who
Michael Grinich - organizer
Kartik Talwar
team
Rob McQueen
Evan Morikawa
Christine Spang
Karim Hamidou
nylas.com@allusers.d.calendar.google.com
Makala Keys
Eben Freeman
Jennie Lees
Ben Gotow
Kavya Joshi

Going?    - -     

Invitation from Google Calendar

You are receiving this email at the account ben@nylas.com because you are subscribed for invitations on calendar Ben Gotow.

To stop receiving these emails, please log in to https://www.google.com/calendar/ and change your notification settings for this calendar.

+ \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_15.html b/spec-nylas/fixtures/emails/email_15.html new file mode 100644 index 000000000..33a25c6bb --- /dev/null +++ b/spec-nylas/fixtures/emails/email_15.html @@ -0,0 +1,102 @@ + + + + + +Test 123


+ +
Quote level 2
+ +
+On Jul 6 2015, at 12:34 pm, spang@nylas.com <spang@nylas.com> wrote:
+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. +

+
+
We agreed on:
+- add a param to the create/update APIs for events instead of creating a new API
+- no body customization for now
+- sending notifications defaults to `false`
+
+

+
+
Differences from what we discussed:
+
- call the parameter `notify_participants` instead of `send_notifications` (it's more explicit about who is getting notified)
+
- only fail if event creation fails, not if the notifications fail to send*
+
- to make failure happen less often, we should validate event participants' email addresses and reject requests with bad email addresses as invalid
+
- on Google, use the `sendNotifications` parameter in the calendar API instead of sending out event notifications ourself, for consistency with expectations and increased reliability
+

+
+
* 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.
+

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

+
+
Samples:
+

+
+
POST /n/<ns-id>/events?notify_participants=true -d '{ ... }'
+
PUT /n/<ns-id>/events?notify_participants=true -d '{ ... }'
+

+
+
Let's ship this and see what Lever thinks.
+
+

+
+
+
+
+On Jul 6 2015, at 9:55 am, Karim Hamidou <karim@nylas.com> wrote:
+
Christine,
+

+
+
Here are my notes about the quick chat we had.
+

+
+
1. We need an invite sending API. We could either:
+
- add a parameter to the event creation/update API to send emails to the participants
+
- or have a separate invite sending endpoint.
+

+
+
We chose to go with the former, because it's simpler.
+

+
+
2. How would this work? 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. 
+
+

+
+
Example: curl -X POST http://localhost:5555/n/namespace_id/events?send_notifications=true  "{ title: 'test event', start: ... }"
+
+

+
+
3. +Error handling.  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. 
+

+
+
Here's what happens when a user creates an event with send_notifications=true:
+
+
1. We create the event in the db
+
2. We try sending emails
+
3. If it failed, we delete the event from the db and return the SMTP error.
+

+
+
Of course API users can try re-sending the same invite as often as they wish.
+

+
+
4. Limitations:
+
- 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)
+
- no support for attached files 
+

+
+
Did I forget anything?
+

+
+
k
+

+
+

+
+
+
+ + diff --git a/spec-nylas/fixtures/emails/email_15_stripped.html b/spec-nylas/fixtures/emails/email_15_stripped.html new file mode 100644 index 000000000..8a0445b41 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_15_stripped.html @@ -0,0 +1,12 @@ + + + + +Test 123


+ + + + + + + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_1_1.txt b/spec-nylas/fixtures/emails/email_1_1.txt new file mode 100755 index 000000000..8b3ba6cb2 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_1.txt @@ -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 diff --git a/spec-nylas/fixtures/emails/email_1_2.txt b/spec-nylas/fixtures/emails/email_1_2.txt new file mode 100755 index 000000000..8697d17e4 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_2.txt @@ -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 diff --git a/spec-nylas/fixtures/emails/email_1_3.txt b/spec-nylas/fixtures/emails/email_1_3.txt new file mode 100755 index 000000000..f7ae6f3e8 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_3.txt @@ -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 diff --git a/spec-nylas/fixtures/emails/email_1_4.txt b/spec-nylas/fixtures/emails/email_1_4.txt new file mode 100755 index 000000000..c79ae1401 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_4.txt @@ -0,0 +1,5 @@ +Awesome! I haven't had another problem with it. + +On Aug 22, 2011, at 7:37 PM, defunkt wrote: + +> Loader seems to be working well. diff --git a/spec-nylas/fixtures/emails/email_1_5.txt b/spec-nylas/fixtures/emails/email_1_5.txt new file mode 100755 index 000000000..249877563 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_5.txt @@ -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. diff --git a/spec-nylas/fixtures/emails/email_1_6.txt b/spec-nylas/fixtures/emails/email_1_6.txt new file mode 100755 index 000000000..8f8f564e8 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_6.txt @@ -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 + +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 diff --git a/spec-nylas/fixtures/emails/email_1_7.txt b/spec-nylas/fixtures/emails/email_1_7.txt new file mode 100644 index 000000000..b374c6aae --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_7.txt @@ -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 diff --git a/spec-nylas/fixtures/emails/email_1_8.txt b/spec-nylas/fixtures/emails/email_1_8.txt new file mode 100644 index 000000000..d4e4121ee --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_8.txt @@ -0,0 +1,37 @@ +On Tue, Apr 29, 2014 at 4:22 PM, Example Dev 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 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 diff --git a/spec-nylas/fixtures/emails/email_1_stripped.html b/spec-nylas/fixtures/emails/email_1_stripped.html new file mode 100644 index 000000000..1bebd39cb --- /dev/null +++ b/spec-nylas/fixtures/emails/email_1_stripped.html @@ -0,0 +1,23 @@ + + + + +
+
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,
+

+Karim
+
From:  Kavya Joshi <kavya@nylas.com>
+
+ + + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_2.html b/spec-nylas/fixtures/emails/email_2.html new file mode 100644 index 000000000..b01d1c838 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_2.html @@ -0,0 +1,148 @@ + + + + + + +
+

It's be great to talk with Jonathan-- feel free to connect us. Thanks.
+

+


+

+

The bug I mentioned manifested itself like this:
+

+


+

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

+
+
+


+

+

But turns out Tom fixed it +here.  I don't think it's yet on PyPI.
+

+


+

+


+

+


+

+
+
+
From: Menno Smits <menno@freshfoo.com>
+Sent: Wednesday, May 27, 2015 2:41 AM
+To: Michael Grinich
+Cc: Christine Spang
+Subject: Re: Thoughts
+
 
+
+
+
Hi Michael,
+
+
 
+
No problems about the delay. I know what it's like.
+
+
 
+
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 (http://tartley.com/). + 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.
+
+
 
+
Are you interested in talking to him? He could give you a full 5 days a week working on IMAPClient and related bits.
+
+
 
+
- Menno
+
+
 
+
p.s. Can you give me details on the bug that you found today?
+
+
 
+
 
+
On Wed, 27 May 2015, at 06:59, Michael Grinich wrote:
+
+
+
Hi Menno,
+
+
 
+
Sorry for the delay. 
+
+
 
+
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.
+
+
 
+
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...)
+
+
 
+
--Michael
+
+
 
+
 
+
 
+
+
On May 21 2015, at 7:44 pm, Menno Smits <menno@freshfoo.com> wrote:
+
+

Hi Michael,
+

+

It was great to talk to you and Christine earlier this week.
+

+

I've been thinking about ways that we could make this work. I'm really
+not ready to leave my current position at Canonical but I'd be prepared
+to consider dropping my hours to work for Nylas part-time. Would you
+consider having me work for Nylas one full day a week if I could
+negotiate my hours down to 4 days a week at Canonical? I think they
+might be open to that (any less could be a struggle).

+

I realise this is probably less of a commitment from me than you'd
+probably like but one day a week would give me much more time to work on
+IMAPClient than I have now. You'll get you the features you need much
+sooner. If this seems workable to you I can start the conversation with
+my managers.

+

I'm also writing to a couple top notch developers that I trust about the
+possibility of working with you on this (they're both in London). I
+believe that one in particular could be thinking about leaving his
+current role.

+

Cheers,
+Menno

+
+
+
 
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_2_1.txt b/spec-nylas/fixtures/emails/email_2_1.txt new file mode 100755 index 000000000..adffd08b7 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_2_1.txt @@ -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. + diff --git a/spec-nylas/fixtures/emails/email_2_stripped.html b/spec-nylas/fixtures/emails/email_2_stripped.html new file mode 100644 index 000000000..94dd6b546 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_2_stripped.html @@ -0,0 +1,68 @@ + + + + + +
+

It's be great to talk with Jonathan-- feel free to connect us. Thanks.
+

+


+

+

The bug I mentioned manifested itself like this:
+

+


+

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

+
+
+


+

+

But turns out Tom fixed it +here.  I don't think it's yet on PyPI.
+

+


+

+


+

+


+

+ +
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_3.html b/spec-nylas/fixtures/emails/email_3.html new file mode 100644 index 000000000..f46bc7816 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_3.html @@ -0,0 +1,47 @@ + + + + + + +
+

You should send these to team@ 
+

+


+

+

hope you feel better! 
+

+


+

+


+

+
+
+
From: Andrea Whiting
+Sent: Monday, April 20, 2015 9:48 AM
+To: Christine Spang; Michael Grinich
+Subject: WFH this morn
+
 
+
+
+
+

Morning!
+

+


+

+

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

+


+

+

Not sure if WFH is a message to email team@ or post on slack in 'general' - lmk what works best.
+

+


+

+

Andrea
+

+
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_3_stripped.html b/spec-nylas/fixtures/emails/email_3_stripped.html new file mode 100644 index 000000000..eadf77e19 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_3_stripped.html @@ -0,0 +1,20 @@ + + + + + +
+

You should send these to team@ 
+

+


+

+

hope you feel better! 
+

+


+

+


+

+ +
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_4.html b/spec-nylas/fixtures/emails/email_4.html new file mode 100644 index 000000000..7c0fc1174 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_4.html @@ -0,0 +1,137 @@ + + + + + + +
+

Hey All,

+


+

+

This was a long email but wanted to do a quick followup on the One Medical portion. I've only received a few responses so far.&n= +bsp;

+


+

+

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= +!?

+


+

+

Get back to me if this is something you want to participate in. Nylas wo= +uld cover the cost. 
+

+


+

+

Thanks,
+

+

Makala 
+

+


+

+


+

+
+
+
+
+
From: Makala Keys
+Sent: Friday, May 29, 2015 4:59 PM
+To: team
+Subject: Revisiting Health Insurance
+
 
+
+
Hi All! +

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

+
+
My original insurance presentation +here , explains w= +hat plans Nylas currently offers.
+
+

+
+
This week I also looked into how to set up a +health savings account. A health savings account is a 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 + +Zenefits account. 
+

+
+
Finally, I researched info about  One Medical Group.&nb= +sp; which is an innovate + +primary care health organization.  One Medical group has seve= +ral locations in San Francisco and a location in Berkeley. It offers an abu= +ndance of services listed +here, making it easier for you to make doctors appointments. = +;
+

+
+
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= +. 
+

+
+
As always, feel free to reach out with questions! 
+

+
+
Makala 
+

+
+

+
+

+
+

+
+
+

+
+
+
+
+
+
+
+
+ + diff --git a/spec-nylas/fixtures/emails/email_4_stripped.html b/spec-nylas/fixtures/emails/email_4_stripped.html new file mode 100644 index 000000000..c764806a6 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_4_stripped.html @@ -0,0 +1,40 @@ + + + + + +
+

Hey All,

+


+

+

This was a long email but wanted to do a quick followup on the One Medical portion. I've only received a few responses so far.&n= +bsp;

+


+

+

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= +!?

+


+

+

Get back to me if this is something you want to participate in. Nylas wo= +uld cover the cost. 
+

+


+

+

Thanks,
+

+

Makala 
+

+


+

+


+

+
+
+ +
+ + + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_5.html b/spec-nylas/fixtures/emails/email_5.html new file mode 100644 index 000000000..c4314df7e --- /dev/null +++ b/spec-nylas/fixtures/emails/email_5.html @@ -0,0 +1,49 @@ +
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:

drew@formation8.com (which is somehow +listed twice)
sam.kwok@garage.com
oprokhorenko@splunk.com

-Andrew
 
From: Jeff Meister <jeff@esper.com>
Date: Mon, Jun 22, 2015 at +4:00 PM
Subject: Nylas bug reports
To: support <support@nylas.com>
Cc: Michael Grinich <mg@nilas.com>, Christine +Spang <spang@nylas.com>, Kavya Joshi <kavya@nylas.com>, Karim +Hamidou <karim@nylas.com>


Hi Nylas +folks,

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:

1. One of our users signed in to Nylas +successfully with email oprokhorenko@splunk.com, 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?

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 han@formation8.com. 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.

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?

Thanks again, and see you on Wednesday!
Jeff
+

+
diff --git a/spec-nylas/fixtures/emails/email_5_stripped.html b/spec-nylas/fixtures/emails/email_5_stripped.html new file mode 100644 index 000000000..ffb70df4a --- /dev/null +++ b/spec-nylas/fixtures/emails/email_5_stripped.html @@ -0,0 +1,7 @@ +
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:

drew@formation8.com (which is somehow +listed twice)
sam.kwok@garage.com
oprokhorenko@splunk.com

-Andrew
 
+ \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_6.html b/spec-nylas/fixtures/emails/email_6.html new file mode 100644 index 000000000..bd180121d --- /dev/null +++ b/spec-nylas/fixtures/emails/email_6.html @@ -0,0 +1,49 @@ + + + + + +

Hello!

+

Here is a summary of the alerts in the past 24 hours:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Alert name# Critical# Warning
mt-st-helena.account-dead-check199.00
mt-st-helena.mysql-redwoodstaging-check02.0
mt-st-helena.mysql-redwoodproduction-check011.0
mt-st-helena.mysql-edgehillproduction-check01.0
mt-st-helena.mysql-edgehillstaging-check09.0
mt-st-helena.mysql-mailsyncstaging-check09.0
+

Have a good day!

+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_6_stripped.html b/spec-nylas/fixtures/emails/email_6_stripped.html new file mode 100644 index 000000000..6734122a6 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_6_stripped.html @@ -0,0 +1,48 @@ + + + + +

Hello!

+

Here is a summary of the alerts in the past 24 hours:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Alert name# Critical# Warning
mt-st-helena.account-dead-check199.00
mt-st-helena.mysql-redwoodstaging-check02.0
mt-st-helena.mysql-redwoodproduction-check011.0
mt-st-helena.mysql-edgehillproduction-check01.0
mt-st-helena.mysql-edgehillstaging-check09.0
mt-st-helena.mysql-mailsyncstaging-check09.0
+

Have a good day!

+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_7.html b/spec-nylas/fixtures/emails/email_7.html new file mode 100644 index 000000000..66ee6591d --- /dev/null +++ b/spec-nylas/fixtures/emails/email_7.html @@ -0,0 +1,255 @@ + + + + + + +
+

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! 
+

+


+

+

And congrats on YC! 
+

+


+

+


+

+
+
+
From: Ted Benson <eob@csail.mit.edu>
+Sent: Monday, April 27, 2015 10:51 AM
+To: Michael Grinich
+Subject: Re: James Tamplin intro?
+
 
+
+
Hey Michael,
+
+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. +
+
+Looking forward to it!
+Ted
+
On Fri, Apr 24, 2015 at 11:55 AM Michael Grinich <mg@nylas.com> wrote:
+
+
+
+

Sure-- bring him along too. See you then. :)
+

+


+

+

--mg
+

+


+

+
+
+
From: +edward.benson@gmail.com <edward.benson@gmail.com> on behalf of Ted Benson <eob@csail.mit.edu>
+Sent: Friday, April 24, 2015 11:39 AM
+To: Michael Grinich
+Subject: Re: James Tamplin intro?
+
 
+
+
+
+
+
+
+
+
+
+
w/ James: Thanks a bunch!
+

+
+
Lunch: Great! Just sent you an invite. Noon OK?
+

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

+
+
Looking forward to seeing you! Mind if Jake (cofounder) comes along for lunch?
+

+
+
+
+

+
On Fri, Apr 24, 2015 at 12:31 AM, Michael Grinich +<mg@nylas.com> wrote:
+
+
Cool, I'll see what I can do wrt James. He might be in Google land these days. 
+

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

+
+
Want to stop by for lunch on Monday? We're at 2030 Harrison St. in SF
+
+

+
+
+
+
On Wed, Apr 22, 2015 at 4:21 PM -0700, "Ted Benson" +<eob@csail.mit.edu> wrote:
+
+
+
+
Hi Michael, +

+
+
No worries about the delay! I've never been to PyCon but heard it's a good time.
+

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

+
+
Connections:
+
Right now we're looking for advice and angel investment. 
+
- If we don't get YC, we're going to start a seed round
+
- 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
+

+
+
In your judgement is that a reasonable way to plan out meetings for the post-YC days we're there. 
+

+
+
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).
+

+
+
I'd love to drop by Monday or Tuesday and see what you all are cooking up!
+

+
+
Thanks again!
+
Ted
+

+
+
   
+
+

+
On Wed, Apr 22, 2015 at 5:03 AM, Michael Grinich +<mgrinich@gmail.com> wrote:
+
+
+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. +
+
+Congrats on making it to the interview circuit!
+regarding networking, are you looking for partnerships or YC advise or investment or something else?
+
+I've actually been coaching a couple startups, one of whichever is pitching YC this wkd. Anything specific you need there?
+
+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. +
+
+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. +
+
+--mg
+
+

+
On Sun, Apr 19, 2015 at 8:45 AM Ted Benson <eob@csail.mit.edu> wrote:
+
+
Hah - I just realized I might have read Facebook wrong. It is WE who are friends, not you and James. +

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

+
On Sun, Apr 19, 2015 at 11:39 AM, Ted Benson +<eob@csail.mit.edu> wrote:
+
+
Hey Michael, +

+
+
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?
+

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

+
+
Company quickie, for context:
+

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

+
+
Let me know -- and thanks either way! 
+

+
+
Cheers,
+
Ted
+ +

+
+

+
+--
+
+
:: Ted Benson
+:: http://people.csail.mit.edu/eob/
+
:: @edwardbenson
+
+
+
+
+
+
+
+
+
+

+
+--
+
+
:: Ted Benson
+:: http://people.csail.mit.edu/eob/
+
:: @edwardbenson
+
+
+
+
+
+
+
+
+
+
+
+

+
+--
+
+
:: Ted Benson
+:: http://people.csail.mit.edu/eob/
+
:: @edwardbenson
+
+
+
+
+
+
+
+
+
+
+
+

+
+--
+
+
:: Ted Benson
+:: http://people.csail.mit.edu/eob/
+
:: @edwardbenson
+
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_7_stripped.html b/spec-nylas/fixtures/emails/email_7_stripped.html new file mode 100644 index 000000000..fc2aad298 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_7_stripped.html @@ -0,0 +1,20 @@ + + + + + +
+

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! 
+

+


+

+

And congrats on YC! 
+

+


+

+


+

+ +
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_8.html b/spec-nylas/fixtures/emails/email_8.html new file mode 100644 index 000000000..5ec55fba7 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_8.html @@ -0,0 +1,58 @@ + + + + + + +
+

What about Haystack? https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf
+

+


+

+


+

+
+

+
+
+
From: Christine Spang
+Sent: Thursday, May 21, 2015 6:52 PM
+To: Nylas Study Group
+Subject: Intros & paper suggestions
+
 
+
+
hey folks, +

+
+
Thought I'd let y'all know who's on this list. Current list members are:
+

+
+
Nylas team
+
Michael Grinich
+
me
+
Kavya Joshi
+
Eben Freeman
+
Jennie Lees
+
Karim Hamidou
+
Evan Morikawa
+
Ben Gotow
+
Rob McQueen
+
Kartik Talwar
+
Andrea Whiting
+
Makala Keys
+

+
+
Friends
+
Nelson Elhage
+
Deborah Hanus
+
Owen Derby
+
Marco Munizaga
+

+
+
Anyone have a paper they're really hankering to read or present in two weeks? :)
+
--spang
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_8_stripped.html b/spec-nylas/fixtures/emails/email_8_stripped.html new file mode 100644 index 000000000..dc34bd1f6 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_8_stripped.html @@ -0,0 +1,16 @@ + + + + + +
+

What about Haystack? https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf
+

+


+

+


+

+ +
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_9.html b/spec-nylas/fixtures/emails/email_9.html new file mode 100644 index 000000000..04a146158 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_9.html @@ -0,0 +1,34 @@ + + + + + +
Hi Christine,  +

+
+
My apologies, please use the below referral code when taking the Insights evaluation: 
+

+
+
+

Go To:  https://online.insights.com/evaluator/SNP/discovery  +   

+

Referral Code: Nylas2015

+


+

+

Please let us know if you have any questions!

+


+

+

Thank you,

+

Eva

+
+

+

+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_9_stripped.html b/spec-nylas/fixtures/emails/email_9_stripped.html new file mode 100644 index 000000000..75e1fbf84 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_9_stripped.html @@ -0,0 +1,31 @@ + + + + +
Hi Christine,  +

+
+
My apologies, please use the below referral code when taking the Insights evaluation: 
+

+
+
+

Go To:  https://online.insights.com/evaluator/SNP/discovery  +   

+

Referral Code: Nylas2015

+


+

+

Please let us know if you have any questions!

+


+

+

Thank you,

+

Eva

+
+

+

+ +
+
+
+
+ + \ No newline at end of file diff --git a/spec-nylas/fixtures/emails/email_BlackBerry.txt b/spec-nylas/fixtures/emails/email_BlackBerry.txt new file mode 100755 index 000000000..9cf4824a0 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_BlackBerry.txt @@ -0,0 +1,3 @@ +Here is another email + +Sent from my BlackBerry diff --git a/spec-nylas/fixtures/emails/email_bullets.txt b/spec-nylas/fixtures/emails/email_bullets.txt new file mode 100755 index 000000000..fb124ab23 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_bullets.txt @@ -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, wrote: + +> Give us an example of how you applied what they learned to achieve +> something in your organization + + + + +-- + +*Joe Smith | Director, Product Management* diff --git a/spec-nylas/fixtures/emails/email_iPhone.txt b/spec-nylas/fixtures/emails/email_iPhone.txt new file mode 100755 index 000000000..e5d2169ff --- /dev/null +++ b/spec-nylas/fixtures/emails/email_iPhone.txt @@ -0,0 +1,3 @@ +Here is another email + +Sent from my iPhone diff --git a/spec-nylas/fixtures/emails/email_multi_word_sent_from_my_mobile_device.txt b/spec-nylas/fixtures/emails/email_multi_word_sent_from_my_mobile_device.txt new file mode 100755 index 000000000..c9f89e292 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_multi_word_sent_from_my_mobile_device.txt @@ -0,0 +1,3 @@ +Here is another email + +Sent from my Verizon Wireless BlackBerry diff --git a/spec-nylas/fixtures/emails/email_sent_from_my_not_signature.txt b/spec-nylas/fixtures/emails/email_sent_from_my_not_signature.txt new file mode 100755 index 000000000..37d1469b8 --- /dev/null +++ b/spec-nylas/fixtures/emails/email_sent_from_my_not_signature.txt @@ -0,0 +1,3 @@ +Here is another email + +Sent from my desk, is much easier then my mobile phone. diff --git a/spec-nylas/quoted-html-parser-spec.coffee b/spec-nylas/quoted-html-parser-spec.coffee new file mode 100644 index 000000000..1bc9fefd1 --- /dev/null +++ b/spec-nylas/quoted-html-parser-spec.coffee @@ -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) diff --git a/spec-nylas/quoted-plain-text-parser-spec.coffee b/spec-nylas/quoted-plain-text-parser-spec.coffee new file mode 100644 index 000000000..3776eabe1 --- /dev/null +++ b/spec-nylas/quoted-plain-text-parser-spec.coffee @@ -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 diff --git a/spec-nylas/utils-spec.coffee b/spec-nylas/utils-spec.coffee index 63e43a901..9909d23a4 100644 --- a/spec-nylas/utils-spec.coffee +++ b/spec-nylas/utils-spec.coffee @@ -348,21 +348,6 @@ describe "isEqual", -> other = {a: 1} ok(!Utils.isEqual(new Foo, other)) -describe "quoted text", -> - it "should be correct for a google calendar invite", -> - - - body = """

Recruiting Email Weekly Blastoff

Turn those cold leads into phone screens! You can make this go super fast by queueing up your drafts before hand and just sending them out during this time.
When
Fri Feb 27, 2015 5pm – 5:30pm Pacific Time
Calendar
Ben Gotow
Who
Michael Grinich - organizer
Kartik Talwar
team
Rob McQueen
Evan Morikawa
Christine Spang
Karim Hamidou
nylas.com@allusers.d.calendar.google.com
Makala Keys
Eben Freeman
Jennie Lees
Ben Gotow
Kavya Joshi

Going?    - -     

Invitation from Google Calendar

You are receiving this email at the account ben@nylas.com because you are subscribed for invitations on calendar Ben Gotow.

To stop receiving these emails, please log in to https://www.google.com/calendar/ and change your notification settings for this calendar.

""" - expect(Utils.quotedTextIndex(body)).toBe(-1) - - it "should be correct when email contains

tags", -> - body = """

Hi Ben

-

Please goto the Workspaces Console > Directories.

-

Then, select the Directory and "Deregister".&nbsp; After you deregister the Directory from Workspaces, you should then be able to goto Directory Services and remove the Directory.

-

Ben

""" - expect(Utils.quotedTextIndex(body)).toBe(-1) - expect(Utils.stripQuotedText(body)).toBe(body) - describe "subjectWithPrefix", -> it "should replace an existing Re:", -> expect(Utils.subjectWithPrefix("Re: Test Case", "Fwd:")).toEqual("Fwd: Test Case") diff --git a/src/flux/models/utils.coffee b/src/flux/models/utils.coffee index d9d362ce5..1d5dc6c46 100644 --- a/src/flux/models/utils.coffee +++ b/src/flux/models/utils.coffee @@ -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|]*>)/) - - # 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
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("]*>')?[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 diff --git a/src/services/quoted-html-parser.coffee b/src/services/quoted-html-parser.coffee new file mode 100644 index 000000000..f4e305448 --- /dev/null +++ b/src/services/quoted-html-parser.coffee @@ -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 diff --git a/src/services/quoted-plain-text-parser.coffee b/src/services/quoted-plain-text-parser.coffee new file mode 100644 index 000000000..2d436954f --- /dev/null +++ b/src/services/quoted-plain-text-parser.coffee @@ -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 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 diff --git a/static/email-frame.less b/static/email-frame.less index 8ac462c67..93258c324 100644 --- a/static/email-frame.less +++ b/static/email-frame.less @@ -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; - } - }