- {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
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 000000000..58011e567
Binary files /dev/null and b/packages/client-app/static/images/thread-list/icon-calendar-@1x.png differ
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 000000000..cc198c18c
Binary files /dev/null and b/packages/client-app/static/images/thread-list/icon-calendar-@2x.png differ
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..a346fb379
--- /dev/null
+++ b/packages/isomorphic-core/src/migrations/20171017123924-alter-messages-add-events.js
@@ -0,0 +1,10 @@
+/* eslint no-unused-vars: 0 */
+
+module.exports = {
+ up: function up(queryInterface, Sequelize) {
+ return Sequelize.query("ALTER TABLE messages ADD events TEXT;");
+ },
+ down: function down(queryInterface, Sequelize) {
+ return true;
+ },
+};