From bc5f6e764484e19c98c674e65532a5a562cc54ec Mon Sep 17 00:00:00 2001 From: Nirmit Date: Thu, 26 Oct 2017 01:41:38 -0400 Subject: [PATCH 1/6] Support to show ICS invite as attachments ICS invites are currently not displayed correctly in Nylas. This commit adds support to extract calendar files and store them as attachments. The attachment is called `Event.ics`. --- packages/client-sync/src/message-processor/extract-files.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client-sync/src/message-processor/extract-files.js b/packages/client-sync/src/message-processor/extract-files.js index 4b0bf9461..fd2d91601 100644 --- a/packages/client-sync/src/message-processor/extract-files.js +++ b/packages/client-sync/src/message-processor/extract-files.js @@ -26,6 +26,7 @@ function collectFilesFromStruct({db, messageValues, struct, fileIds = new Set()} // to ensure that there is a filename and contentId because some clients // use "inline" for text in the body. const isAttachment = /(attachment)/gi.test(disposition.type) || + /(calendar)/gi.test(part.subtype) || (/(inline)/gi.test(disposition.type) && filename && contentId); if (!isAttachment) continue From fe416295240d97df28a1af1b3aa00fc198d67a34 Mon Sep 17 00:00:00 2001 From: Nirmit Date: Thu, 26 Oct 2017 12:31:12 -0400 Subject: [PATCH 2/6] FIX: Only scroll vertically Fixes #135 --- packages/client-app/static/components/search-bar.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client-app/static/components/search-bar.less b/packages/client-app/static/components/search-bar.less index 4742b5ff2..1c99219dc 100644 --- a/packages/client-app/static/components/search-bar.less +++ b/packages/client-app/static/components/search-bar.less @@ -34,7 +34,7 @@ top: 23px; z-index: 2; width: 100%; - overflow: scroll; + overflow-y: scroll; max-height: 50vh; box-shadow: @standard-shadow; From e0831a6232d7102eabff0f0dabfcd955abb24e2a Mon Sep 17 00:00:00 2001 From: NB Date: Mon, 30 Oct 2017 23:06:39 -0400 Subject: [PATCH 3/6] Enable storing invites from text/calendar Parse the raw email body for text/calendar part and store the decoded content (ICAL). We will then use this content to show beautiful event headers. --- .../client-app/src/flux/models/message.es6 | 8 ++--- .../fetch-messages-in-folder.imap.es6 | 8 ++++- packages/client-sync/src/models/message.js | 1 + .../isomorphic-core/src/message-utils.es6 | 25 +++++++++++++- ...0171017123924-alter-messages-add-events.js | 34 +++++++++++++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 packages/isomorphic-core/src/migrations/20171017123924-alter-messages-add-events.js diff --git a/packages/client-app/src/flux/models/message.es6 b/packages/client-app/src/flux/models/message.es6 index 2a134e002..43e3b37a7 100644 --- a/packages/client-app/src/flux/models/message.es6 +++ b/packages/client-app/src/flux/models/message.es6 @@ -135,9 +135,9 @@ export default class Message extends ModelWithMetadata { modelKey: 'unread', }), - events: Attributes.Collection({ + events: Attributes.String({ modelKey: 'events', - itemClass: Event, + /* itemClass: Event, */ }), starred: Attributes.Boolean({ @@ -229,10 +229,6 @@ Message(date DESC) WHERE draft = 1`, json.object = 'draft' } - if (this.events && this.events.length) { - json.event_id = this.events[0].serverId - } - return json } diff --git a/packages/client-sync/src/local-sync-worker/sync-tasks/fetch-messages-in-folder.imap.es6 b/packages/client-sync/src/local-sync-worker/sync-tasks/fetch-messages-in-folder.imap.es6 index df3ea9ced..3e79b3fa3 100644 --- a/packages/client-sync/src/local-sync-worker/sync-tasks/fetch-messages-in-folder.imap.es6 +++ b/packages/client-sync/src/local-sync-worker/sync-tasks/fetch-messages-in-folder.imap.es6 @@ -158,7 +158,7 @@ class FetchMessagesInFolderIMAP extends SyncTask { const desired = []; const available = []; const unseen = [struct]; - const desiredTypes = new Set(['text/plain', 'text/html']); + const desiredTypes = new Set(['text/plain', 'text/html', 'text/calendar']); // MIME structures can be REALLY FREAKING COMPLICATED. To simplify // processing, we flatten the MIME structure by walking it depth-first, // throwing away all multipart headers with the exception of @@ -177,6 +177,12 @@ class FetchMessagesInFolderIMAP extends SyncTask { part.shift(); const alternativeParts = this._getDesiredMIMEParts(part); if (alternativeParts.length > 0) { + // We need the ICS body to capture the invite information + alternativeParts.forEach( function(alternativePart) { + if( alternativePart.mimeType == 'text/calendar') { + desired.push(alternativePart); + } + }); // With reference to RFC2046, we keep only the last supported part. desired.push(alternativeParts[alternativeParts.length - 1]); } diff --git a/packages/client-sync/src/models/message.js b/packages/client-sync/src/models/message.js index 012ff09e0..615869a47 100644 --- a/packages/client-sync/src/models/message.js +++ b/packages/client-sync/src/models/message.js @@ -42,6 +42,7 @@ module.exports = (sequelize, Sequelize) => { })); }, }, + events: Sequelize.TEXT, subject: Sequelize.STRING(500), snippet: Sequelize.STRING(255), date: Sequelize.DATE, diff --git a/packages/isomorphic-core/src/message-utils.es6 b/packages/isomorphic-core/src/message-utils.es6 index 4f33e1279..415c85797 100644 --- a/packages/isomorphic-core/src/message-utils.es6 +++ b/packages/isomorphic-core/src/message-utils.es6 @@ -171,7 +171,9 @@ function bodyFromParts(imapMessage, desiredParts) { // // This may seem kind of weird, but some MUAs _do_ send out whack stuff // like an HTML body followed by a plaintext footer. - if (mimeType === 'text/plain') { + if ( mimeType === 'text/calendar' ) { + + } else if (mimeType === 'text/plain') { body += htmlifyPlaintext(decoded); } else { body += decoded; @@ -184,6 +186,26 @@ function bodyFromParts(imapMessage, desiredParts) { return body; } +// Parse events from text/calendar part of the email +function parseEvents(imapMessage, desiredParts) { + let body = ''; + for (const {id, mimeType, transferEncoding, charset} of desiredParts) { + let decoded = ''; + if ((/quot(ed)?[-/]print(ed|able)?/gi).test(transferEncoding)) { + decoded = mimelib.decodeQuotedPrintable(imapMessage.parts[id], charset); + } else if ((/base64/gi).test(transferEncoding)) { + decoded = mimelib.decodeBase64(imapMessage.parts[id], charset); + } else { + decoded = encoding.convert(imapMessage.parts[id], 'utf-8', charset).toString('utf-8'); + } + + if ( mimeType === 'text/calendar' && (/base64/gi).test(transferEncoding) ) { + body += decoded; + } + } + return body; +} + // Since we only fetch the MIME structure and specific desired MIME parts from // IMAP, we unfortunately can't use an existing library like mailparser to parse // the message, and have to do fun stuff like deal with character sets and @@ -210,6 +232,7 @@ async function parseFromImap(imapMessage, desiredParts, {db, accountId, folder}) replyTo: parseContacts(parsedHeaders['reply-to']), accountId: accountId, body: bodyFromParts(imapMessage, desiredParts), + events: parseEvents(imapMessage, desiredParts), snippet: null, unread: !attributes.flags.includes('\\Seen'), starred: attributes.flags.includes('\\Flagged'), diff --git a/packages/isomorphic-core/src/migrations/20171017123924-alter-messages-add-events.js b/packages/isomorphic-core/src/migrations/20171017123924-alter-messages-add-events.js new file mode 100644 index 000000000..bc89a59fd --- /dev/null +++ b/packages/isomorphic-core/src/migrations/20171017123924-alter-messages-add-events.js @@ -0,0 +1,34 @@ +/* eslint no-unused-vars: 0 */ + +module.exports = { + up: function up(queryInterface, Sequelize) { + return queryInterface.createTable('Users', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + first_name: { + type: Sequelize.STRING, + }, + last_name: { + type: Sequelize.STRING, + }, + bio: { + type: Sequelize.TEXT, + }, + createdAt: { + allowNull: false, + type: Sequelize.DATE, + }, + updatedAt: { + allowNull: false, + type: Sequelize.DATE, + }, + }); + }, + down: function down(queryInterface, Sequelize) { + return queryInterface.dropTable('Users'); + }, +}; From 5eb5a54dc3ee295e8df75fb91bd9c365d78ac0a3 Mon Sep 17 00:00:00 2001 From: NB Date: Tue, 31 Oct 2017 10:30:12 -0400 Subject: [PATCH 4/6] Show calendar icon for invites in the thread list Displays a calendar icon for threads that have a calendar invite associated with atleast one of the messages in the thread. --- .../thread-list/lib/thread-list-columns.cjsx | 13 +++++++++++++ packages/client-app/src/flux/models/utils.coffee | 4 ++++ .../images/thread-list/icon-calendar-@1x.png | Bin 0 -> 754 bytes .../images/thread-list/icon-calendar-@2x.png | Bin 0 -> 1082 bytes 4 files changed, 17 insertions(+) create mode 100644 packages/client-app/static/images/thread-list/icon-calendar-@1x.png create mode 100644 packages/client-app/static/images/thread-list/icon-calendar-@2x.png diff --git a/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx b/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx index 09a3735cd..027bfb2b4 100644 --- a/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx +++ b/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx @@ -92,8 +92,14 @@ c3 = new ListTabular.Column flex: 4 resolver: (thread) => attachment = false + invite = false + messages = thread.__messages || [] + hasInvite = thread.hasAttachments and messages.find (m) -> Utils.showIconForInvites(m.files) + if hasInvite + invite =
+ hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files) if hasAttachments attachment =
@@ -102,6 +108,7 @@ c3 = new ListTabular.Column {subject(thread.subject)} {getSnippet(thread)} + {invite} {attachment} @@ -142,8 +149,13 @@ cNarrow = new ListTabular.Column resolver: (thread) => pencil = false attachment = false + invite = false messages = thread.__messages || [] + hasInvite = thread.hasAttachments and messages.find (m) -> Utils.showIconForInvites(m.files) + if hasInvite + invite =
+ hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files) if hasAttachments attachment =
@@ -177,6 +189,7 @@ cNarrow = new ListTabular.Column {pencil} + {invite} {attachment} !f.contentId or f.size > 12 * 1024 + showIconForInvites: (files) -> + return false unless files instanceof Array + return files.find (f) -> `f.contentType == "text/calendar"`; + extractTextFromHtml: (html, {maxLength} = {}) -> if (html ? "").trim().length is 0 then return "" if maxLength and html.length > maxLength diff --git a/packages/client-app/static/images/thread-list/icon-calendar-@1x.png b/packages/client-app/static/images/thread-list/icon-calendar-@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..58011e5678e1387d38466019d438d36a7a05ee20 GIT binary patch literal 754 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW zg+Z8+Vb&Z8pn}NEkcg59UmvUF{9L`nl>DSry^7odplSvNn+hu+GdHy)QK2F?C$HG5 z!d3~a!V1U+3F|8Y3;nDA{o-C@9zzrKDK}xwt{K19`Se z86_nJR{Hwo<>h+i#(Mch>H3D2mX`VkM*2oZx=P7{9O-#x!EwNQn0$BtH5O0c3d|4@L;p!@;Rg)2@GT-PZ!4!3&Ew6 zY`s_<1>CBy%ZrGoI35h?;k9kHUobQ7SapT)=H4R#!o9ABMh1c+f~m*9w`skMm5kZ& zz&E`7YWUsYyGy5BYQ8n?)yXOLvcE6tO|am1{~-8S=5f@5`5T>Uimy(wi+IYkE_?%Nn`khV^0>)Yp@8yl-Fx-V9qbw2Vg zDfaAz$LpJfSI<9ewTE?Hfb?On4`(VEZf{^rWq4QeD#gt>{Op0eDz3A#CjWnL{#sZ6 z=8tts-g&05OE$}v{aF2Vm;KD!PwdN<{}cRK-nffBa!qZbthDs!??&Z|*w-s=@{FCf z_vD>p!cY3t7k-M`>{+Uqe|oN{y!(QyyDXZ1T|N6~^~>kd8#?qaX_x4(7b`zx^z59z a!9O;Ae(w(zM(Te+3DMKl&t;ucLK6T+?iW7* literal 0 HcmV?d00001 diff --git a/packages/client-app/static/images/thread-list/icon-calendar-@2x.png b/packages/client-app/static/images/thread-list/icon-calendar-@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cc198c18c539abb9f8aaba9ec405c3258b830ef7 GIT binary patch literal 1082 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wkP(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@WME*b@^o%v|?2y%gvoihJns5ytjTlM-QNDL-v7VP;)~K}Gpwg6mj?FaCR-ZId7Hfax%6A~!DlYnyXNn> zzI^-7jq?|Vyj-=$j&)s%oVoUg-**nLKlijRBKGT+C;OFT-**-(Etru0Xo*L|7Ve}u z-Nzrz%dqFF)Y^80^W*jxnPG-8XP24nV`=7(^4zAM#4_>XM)wKKAEOnFcGdkku~Cuf z`MO=dv|1+%{@6J~tf9~L_R{SAbx)Ee^%n9SQod;PT>Me_$0?H;QcB)1hKUQj-e7rH zLE)>juU^r$3Z;{CANo8nf3)d`p@5`pbxiiP8Q1ns{kK%&_g=1TeIO+oq92@hXFqaM zFtVB?)Uhtv>WBZ!>qlJ+<7(8Go&BnFVD66H_FN3L_uf~j%s+hO{KnTzawim6ueZN; zQ&)NXjqy*xjpi4}k4Rjr)cTQlSoYApo*w~koK`ZtyLkQ4It$exQxD;?E#~r`T(gue zS14|CHCOp4^@lA+bMB-c`y74lzj~sSzSxA(t;aC5`i>Ag+vLZa1SVI+YKR_qQNVCQ zE_JEIBZo6OVPO{qIN6G1MGa;h$Y5H`Ib~jpjg{@p-TNB6_?44Bu&AVotq)=7_qoS> zp#RCan+fjrbw8J#cr=x3#0zX`b42BC?kM!_h#pAn$ Date: Tue, 31 Oct 2017 10:30:12 -0400 Subject: [PATCH 5/6] Show calendar icon for invites in the thread list Displays a calendar icon for threads that have a calendar invite associated with atleast one of the messages in the thread. --- .../thread-list/lib/thread-list-columns.cjsx | 13 +++++++++++++ .../thread-list/stylesheets/thread-list.less | 6 ++++++ packages/client-app/src/flux/models/utils.coffee | 4 ++++ .../images/thread-list/icon-calendar-@1x.png | Bin 0 -> 754 bytes .../images/thread-list/icon-calendar-@2x.png | Bin 0 -> 1082 bytes 5 files changed, 23 insertions(+) create mode 100644 packages/client-app/static/images/thread-list/icon-calendar-@1x.png create mode 100644 packages/client-app/static/images/thread-list/icon-calendar-@2x.png diff --git a/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx b/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx index 09a3735cd..027bfb2b4 100644 --- a/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx +++ b/packages/client-app/internal_packages/thread-list/lib/thread-list-columns.cjsx @@ -92,8 +92,14 @@ c3 = new ListTabular.Column flex: 4 resolver: (thread) => attachment = false + invite = false + messages = thread.__messages || [] + hasInvite = thread.hasAttachments and messages.find (m) -> Utils.showIconForInvites(m.files) + if hasInvite + invite =
+ hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files) if hasAttachments attachment =
@@ -102,6 +108,7 @@ c3 = new ListTabular.Column {subject(thread.subject)} {getSnippet(thread)} + {invite} {attachment} @@ -142,8 +149,13 @@ cNarrow = new ListTabular.Column resolver: (thread) => pencil = false attachment = false + invite = false messages = thread.__messages || [] + hasInvite = thread.hasAttachments and messages.find (m) -> Utils.showIconForInvites(m.files) + if hasInvite + invite =
+ hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files) if hasAttachments attachment =
@@ -177,6 +189,7 @@ cNarrow = new ListTabular.Column {pencil} + {invite} {attachment} !f.contentId or f.size > 12 * 1024 + showIconForInvites: (files) -> + return false unless files instanceof Array + return files.find (f) -> `f.contentType == "text/calendar"`; + extractTextFromHtml: (html, {maxLength} = {}) -> if (html ? "").trim().length is 0 then return "" if maxLength and html.length > maxLength diff --git a/packages/client-app/static/images/thread-list/icon-calendar-@1x.png b/packages/client-app/static/images/thread-list/icon-calendar-@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..58011e5678e1387d38466019d438d36a7a05ee20 GIT binary patch literal 754 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0WW zg+Z8+Vb&Z8pn}NEkcg59UmvUF{9L`nl>DSry^7odplSvNn+hu+GdHy)QK2F?C$HG5 z!d3~a!V1U+3F|8Y3;nDA{o-C@9zzrKDK}xwt{K19`Se z86_nJR{Hwo<>h+i#(Mch>H3D2mX`VkM*2oZx=P7{9O-#x!EwNQn0$BtH5O0c3d|4@L;p!@;Rg)2@GT-PZ!4!3&Ew6 zY`s_<1>CBy%ZrGoI35h?;k9kHUobQ7SapT)=H4R#!o9ABMh1c+f~m*9w`skMm5kZ& zz&E`7YWUsYyGy5BYQ8n?)yXOLvcE6tO|am1{~-8S=5f@5`5T>Uimy(wi+IYkE_?%Nn`khV^0>)Yp@8yl-Fx-V9qbw2Vg zDfaAz$LpJfSI<9ewTE?Hfb?On4`(VEZf{^rWq4QeD#gt>{Op0eDz3A#CjWnL{#sZ6 z=8tts-g&05OE$}v{aF2Vm;KD!PwdN<{}cRK-nffBa!qZbthDs!??&Z|*w-s=@{FCf z_vD>p!cY3t7k-M`>{+Uqe|oN{y!(QyyDXZ1T|N6~^~>kd8#?qaX_x4(7b`zx^z59z a!9O;Ae(w(zM(Te+3DMKl&t;ucLK6T+?iW7* literal 0 HcmV?d00001 diff --git a/packages/client-app/static/images/thread-list/icon-calendar-@2x.png b/packages/client-app/static/images/thread-list/icon-calendar-@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cc198c18c539abb9f8aaba9ec405c3258b830ef7 GIT binary patch literal 1082 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wkP(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@WME*b@^o%v|?2y%gvoihJns5ytjTlM-QNDL-v7VP;)~K}Gpwg6mj?FaCR-ZId7Hfax%6A~!DlYnyXNn> zzI^-7jq?|Vyj-=$j&)s%oVoUg-**nLKlijRBKGT+C;OFT-**-(Etru0Xo*L|7Ve}u z-Nzrz%dqFF)Y^80^W*jxnPG-8XP24nV`=7(^4zAM#4_>XM)wKKAEOnFcGdkku~Cuf z`MO=dv|1+%{@6J~tf9~L_R{SAbx)Ee^%n9SQod;PT>Me_$0?H;QcB)1hKUQj-e7rH zLE)>juU^r$3Z;{CANo8nf3)d`p@5`pbxiiP8Q1ns{kK%&_g=1TeIO+oq92@hXFqaM zFtVB?)Uhtv>WBZ!>qlJ+<7(8Go&BnFVD66H_FN3L_uf~j%s+hO{KnTzawim6ueZN; zQ&)NXjqy*xjpi4}k4Rjr)cTQlSoYApo*w~koK`ZtyLkQ4It$exQxD;?E#~r`T(gue zS14|CHCOp4^@lA+bMB-c`y74lzj~sSzSxA(t;aC5`i>Ag+vLZa1SVI+YKR_qQNVCQ zE_JEIBZo6OVPO{qIN6G1MGa;h$Y5H`Ib~jpjg{@p-TNB6_?44Bu&AVotq)=7_qoS> zp#RCan+fjrbw8J#cr=x3#0zX`b42BC?kM!_h#pAn$ Date: Thu, 2 Nov 2017 00:06:27 -0400 Subject: [PATCH 6/6] show invite information from ICS data --- .../events/lib/event-header.cjsx | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/packages/client-app/internal_packages/events/lib/event-header.cjsx b/packages/client-app/internal_packages/events/lib/event-header.cjsx index f94b4246f..8c1f7fdef 100644 --- a/packages/client-app/internal_packages/events/lib/event-header.cjsx +++ b/packages/client-app/internal_packages/events/lib/event-header.cjsx @@ -1,7 +1,9 @@ + _ = require 'underscore' path = require 'path' React = require 'react' {RetinaImg} = require 'nylas-component-kit' +icsParser = require './ics-parser' {Actions, DateUtils, Message, @@ -19,47 +21,61 @@ class EventHeader extends React.Component message: React.PropTypes.instanceOf(Message).isRequired constructor: (@props) -> + if this.props.message.events.length > 12 + eventContent = icsParser.convert( @props.message.events ); + else + return null; @state = - event: @props.message.events[0] + event: eventContent + + @isEventValid = typeof eventContent == "object"; + calendarObj = @state.event.VCALENDAR[0]; + + @eventDetails = { + "title": calendarObj.VEVENT.SUMMARY, + "start_time": moment(calendarObj.VEVENT.DTSTART + calendarObj.VTIMEZONE.DAYLIGHT.TZOFFSETTO).tz(DateUtils.timeZone), + "end_time": moment(calendarObj.VEVENT.DTEND + calendarObj.VTIMEZONE.DAYLIGHT.TZOFFSETTO).tz(DateUtils.timeZone), + "location": calendarObj.VEVENT.LOCATION, + "description": calendarObj.VEVENT.DESCRIPTION, + "status": calendarObj.VEVENT.STATUS + } _onChange: => - return unless @state.event - DatabaseStore.find(Event, @state.event.id).then (event) => - return unless event - @setState({event}) + # TODO + return componentDidMount: => # TODO: This should use observables! - @_unlisten = DatabaseStore.listen (change) => - if @state.event and change.objectClass is Event.name - updated = _.find change.objects, (o) => o.id is @state.event.id - @setState({event: updated}) if updated - @_onChange() + return componentWillReceiveProps: (nextProps) => - @setState({event:nextProps.message.events[0]}) - @_onChange() + # TODO + return componentWillUnmount: => - @_unlisten?() + #TODO + return render: => timeFormat = DateUtils.getTimeFormat({timeZone: true}) - if @state.event? + if @isEventValid?
- Event: {@state.event.title} + Event: {@eventDetails.title}
- {moment(@state.event.start*1000).tz(DateUtils.timeZone).format("dddd, MMMM Do")} + {@eventDetails.start_time.format("dddd, MMMM Do")}
- {moment(@state.event.start*1000).tz(DateUtils.timeZone).format(timeFormat)} + {@eventDetails.start_time.format("h:mm a") + " - " + @eventDetails.end_time.format("h:mm a")} +
+
+ {@eventDetails.description}
{@_renderEventActions()}
@@ -70,23 +86,11 @@ class EventHeader extends React.Component
_renderEventActions: => - me = @state.event.participantForMe() - return false unless me - - actions = [["yes", "Accept"], ["maybe", "Maybe"], ["no", "Decline"]] - -
- {actions.map ([status, label]) => - classes = "btn-rsvp " - classes += status if me.status is status -
@_rsvp(status)}> - {label} -
- } -
+ # TODO Later + return _rsvp: (status) => - me = @state.event.participantForMe() - Actions.queueTask(new EventRSVPTask(@state.event, me.email, status)) + # TODO Later + return module.exports = EventHeader