From abf8ea1cf8715bfb6f28c4c0f2a924e5e5f8bae2 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 4 May 2016 09:36:25 +0200 Subject: show import button if attachment is of type 'text/calendar' --- css/mail.css | 6 +++++- js/service/attachmentservice.js | 16 ++++++++++++++++ js/templates/message-attachment.html | 3 +++ js/views/messageattachment.js | 31 +++++++++++++++++++++++++++++-- lib/controller/messagescontroller.php | 19 +++++++++++++++++-- 5 files changed, 70 insertions(+), 5 deletions(-) diff --git a/css/mail.css b/css/mail.css index f6aa0b746..8a5172a1c 100755 --- a/css/mail.css +++ b/css/mail.css @@ -663,7 +663,8 @@ input.submit-message, max-height: 120px; } .attachment-save-to-cloud, -.attachment-download { +.attachment-download, +.attachment-import { position: absolute; height: 32px; width: 32px; @@ -675,6 +676,9 @@ input.submit-message, .attachment-download { right: 41px; } +.attachment-import { + right: 79px; +} /* show icon + text for Download all button as well as when there is only one attachment */ .attachments-save-to-cloud, diff --git a/js/service/attachmentservice.js b/js/service/attachmentservice.js index a231e9289..0d21bdfa3 100644 --- a/js/service/attachmentservice.js +++ b/js/service/attachmentservice.js @@ -25,6 +25,7 @@ define(function(require) { var Radio = require('radio'); Radio.message.reply('save:cloud', saveToFiles); + Radio.message.reply('attachment:download', downloadAttachment); /** * @param {Account} account @@ -63,4 +64,19 @@ define(function(require) { return defer.promise(); } + function downloadAttachment(url) { + var defer = $.Deferred(); + + $.ajax(url, { + success: function(data) { + defer.resolve(data); + }, + error: function() { + defer.reject(); + } + }); + + return defer.promise(); + } + }); diff --git a/js/templates/message-attachment.html b/js/templates/message-attachment.html index 9f902ca28..7fab1c1f1 100644 --- a/js/templates/message-attachment.html +++ b/js/templates/message-attachment.html @@ -4,5 +4,8 @@ {{/if}} {{fileName}} ({{humanFileSize size}}) +{{#if isCalendarEvent}} + +{{/if}} \ No newline at end of file diff --git a/js/views/messageattachment.js b/js/views/messageattachment.js index 7746b02d0..4cb80998a 100644 --- a/js/views/messageattachment.js +++ b/js/views/messageattachment.js @@ -23,6 +23,7 @@ define(function(require) { var $ = require('jquery'); var Handlebars = require('handlebars'); var Marionette = require('marionette'); + var Radio = require('radio'); var MessageController = require('controller/messagecontroller'); var MessageAttachmentTemplate = require('text!templates/message-attachment.html'); @@ -33,11 +34,13 @@ define(function(require) { template: Handlebars.compile(MessageAttachmentTemplate), ui: { 'downloadButton': '.attachment-download', - 'saveToCloudButton': '.attachment-save-to-cloud' + 'saveToCloudButton': '.attachment-save-to-cloud', + 'importCalendarEventButton': '.attachment-import.calendar' }, events: { 'click': '_onDownload', - 'click @ui.saveToCloudButton': '_onSaveToCloud' + 'click @ui.saveToCloudButton': '_onSaveToCloud', + 'click @ui.importCalendarEventButton': '_onImportCalendarEvent' }, _onDownload: function(e) { if (!e.isDefaultPrevented()) { @@ -67,6 +70,30 @@ define(function(require) { .removeClass('icon-loading-small') .prop('disabled', false); }); + }, + _onImportCalendarEvent: function(e) { + e.preventDefault(); + + this.ui.importCalendarEventButton + .removeClass('icon-add') + .addClass('icon-loading-small'); + + var url = this.model.get('downloadUrl'); + var downloading = Radio.message.request('attachment:download', url); + + var _this = this; + $.when(downloading).done(function(data) { + console.log(data); + }); + $.when(downloading.fail(function() { + Radio.ui.trigger('error:show', t('Error while downloading calendar event')); + })); + $.when(downloading).always(function() { + _this.ui.importCalendarEventButton + .removeClass('icon-loading-small') + .addClass('icon-add'); + }); + } }); diff --git a/lib/controller/messagescontroller.php b/lib/controller/messagescontroller.php index 1b3ddb168..6bc689b15 100755 --- a/lib/controller/messagescontroller.php +++ b/lib/controller/messagescontroller.php @@ -397,6 +397,8 @@ class MessagesController extends Controller { if ($this->attachmentIsImage($attachment)) { $attachment['isImage'] = true; + } else if ($this->attachmentIsCalendarEvent($attachment)) { + $attachment['isCalendarEvent'] = true; } return $attachment; } @@ -405,11 +407,24 @@ class MessagesController extends Controller { * @param $attachment * * Determines if the content of this attachment is an image + * + * @return boolean */ private function attachmentIsImage($attachment) { - return in_array($attachment['mime'], array('image/jpeg', + return in_array( + $attachment['mime'], [ + 'image/jpeg', 'image/png', - 'image/gif')); + 'image/gif' + ]); + } + + /** + * @param type $attachment + * @return boolean + */ + private function attachmentIsCalendarEvent($attachment) { + return $attachment['mime'] === 'text/calendar'; } /** -- cgit v1.2.3 From d03da7c7e5192bd170f657bc9ace81e457fb87e9 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 4 May 2016 11:31:11 +0200 Subject: fetch and show list of calendars --- bower.json | 4 +- css/mail.css | 8 +++ js/app.js | 4 ++ js/models/dav/calendar.js | 26 +++++++++ js/radio.js | 16 +++--- js/require_config.js | 5 ++ js/service/davservice.js | 108 +++++++++++++++++++++++++++++++++++ js/templates/calendar.html | 1 + js/templates/message-attachment.html | 3 +- js/views/app.js | 4 ++ js/views/calendarspopoverview.js | 30 ++++++++++ js/views/calendarview.js | 31 ++++++++++ js/views/messageattachment.js | 43 +++++++++++--- 13 files changed, 265 insertions(+), 18 deletions(-) create mode 100644 js/models/dav/calendar.js create mode 100644 js/service/davservice.js create mode 100644 js/templates/calendar.html create mode 100644 js/views/calendarspopoverview.js create mode 100644 js/views/calendarview.js diff --git a/bower.json b/bower.json index 8e81edf0d..1daeb3bf7 100644 --- a/bower.json +++ b/bower.json @@ -25,6 +25,8 @@ "jquery-visibility": "~1.0.11", "requirejs": "2.2.0", "text": "^2.0.14", - "underscore": "~1.8.3" + "underscore": "~1.8.3", + "davclient.js": "git@github.com:evert/davclient.js.git", + "es6-promise": "^3.2.1" } } diff --git a/css/mail.css b/css/mail.css index 8a5172a1c..cf97c2a2d 100755 --- a/css/mail.css +++ b/css/mail.css @@ -679,6 +679,14 @@ input.submit-message, .attachment-import { right: 79px; } +.attachment-import-popover { + right: -17px; + top: 56px; +} +.attachment-import-popover::after { + left: 30px; + right: initial; +} /* show icon + text for Download all button as well as when there is only one attachment */ .attachments-save-to-cloud, diff --git a/js/app.js b/js/app.js index 57cfb47dc..67371932a 100644 --- a/js/app.js +++ b/js/app.js @@ -22,6 +22,9 @@ define(function(require) { 'use strict'; + // Enable ES6 promise polyfill + require('es6-promise').polyfill(); + var $ = require('jquery'); var Backbone = require('backbone'); var Handlebars = require('handlebars'); @@ -39,6 +42,7 @@ define(function(require) { require('controller/messagecontroller'); require('service/accountservice'); require('service/attachmentservice'); + require('service/davservice'); require('service/folderservice'); require('service/messageservice'); require('notification'); diff --git a/js/models/dav/calendar.js b/js/models/dav/calendar.js new file mode 100644 index 000000000..e7f0b18bb --- /dev/null +++ b/js/models/dav/calendar.js @@ -0,0 +1,26 @@ +/** + * @author Christoph Wurst + * + * ownCloud - Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +define(function(require) { + 'use strict'; + + var Backbone = require('backbone'); + + return Backbone.Model.extend({}); +}); \ No newline at end of file diff --git a/js/radio.js b/js/radio.js index f365b0ab8..306bcb009 100644 --- a/js/radio.js +++ b/js/radio.js @@ -13,22 +13,24 @@ define(function(require) { var Radio = require('backbone.radio'); - var uiChannel = Radio.channel('ui'); - var notificationChannel = Radio.channel('notification'); - var stateChannel = Radio.channel('state'); var accountChannel = Radio.channel('account'); var folderChannel = Radio.channel('folder'); + var davChannel = Radio.channel('dav'); var messageChannel = Radio.channel('message'); var navigationChannel = Radio.channel('navigation'); + var notificationChannel = Radio.channel('notification'); + var stateChannel = Radio.channel('state'); + var uiChannel = Radio.channel('ui'); var channels = { - ui: uiChannel, - notification: notificationChannel, - state: stateChannel, account: accountChannel, + dav: davChannel, folder: folderChannel, message: messageChannel, - navigation: navigationChannel + navigation: navigationChannel, + notification: notificationChannel, + state: stateChannel, + ui: uiChannel }; // Log all events to the console diff --git a/js/require_config.js b/js/require_config.js index a990956f9..d2a272f62 100644 --- a/js/require_config.js +++ b/js/require_config.js @@ -21,11 +21,16 @@ */ backbone: 'vendor/backbone/backbone', 'backbone.radio': 'vendor/backbone.radio/build/backbone.radio', + davclient: 'vendor/davclient.js/lib/client', domready: 'vendor/domReady/domReady', + 'es6-promise': 'vendor/es6-promise/es6-promise.min', handlebars: 'vendor/handlebars/handlebars', marionette: 'vendor/backbone.marionette/lib/backbone.marionette', underscore: 'vendor/underscore/underscore', text: 'vendor/text/text' + }, + davclient: { + exports: 'dav' } }); diff --git a/js/service/davservice.js b/js/service/davservice.js new file mode 100644 index 000000000..edf83e7c9 --- /dev/null +++ b/js/service/davservice.js @@ -0,0 +1,108 @@ +/** + * @author Christoph Wurst + * + * ownCloud - Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +define(function(require) { + 'use strict'; + + var _ = require('underscore'); + var $ = require('jquery'); + var Backbone = require('backbone'); + var dav = require('davclient'); + var OC = require('OC'); + var Radio = require('radio'); + var Calendar = require('models/dav/calendar'); + + Radio.dav.reply('calendars', getUserCalendars); + + var client = new dav.Client({ + baseUrl: OC.linkToRemote('dav/calendars'), + xmlNamespaces: { + 'DAV:': 'd', + 'urn:ietf:params:xml:ns:caldav': 'c', + 'http://apple.com/ns/ical/': 'aapl', + 'http://owncloud.org/ns': 'oc', + 'http://calendarserver.org/ns/': 'cs' + } + }); + var props = [ + '{DAV:}displayname', + '{urn:ietf:params:xml:ns:caldav}calendar-description', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone', + '{http://apple.com/ns/ical/}calendar-order', + '{http://apple.com/ns/ical/}calendar-color', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set', + '{http://owncloud.org/ns}calendar-enabled', + '{DAV:}acl', + '{DAV:}owner', + '{http://owncloud.org/ns}invite' + ]; + + function getResponseCodeFromHTTPResponse(t) { + return parseInt(t.split(' ')[1]); + } + + function getCalendarData(properties) { + var data = { + displayname: properties['{DAV:}displayname'], + color: properties['{http://apple.com/ns/ical/}calendar-color'], + order: properties['{http://apple.com/ns/ical/}calendar-order'], + components: { + vevent: false + } + }; + + var components = properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] || []; + for (var i = 0; i < components.length; i++) { + var name = components[i].attributes.getNamedItem('name').textContent.toLowerCase(); + if (data.components.hasOwnProperty(name)) { + data.components[name] = true; + } + } + + return data; + } + + function getUserCalendars() { + var defer = $.Deferred(); + var url = OC.linkToRemote('dav/calendars') + '/' + OC.currentUser + '/'; + + client.propFind(url, props, 1, { + 'requesttoken': OC.requestToken + }).then(function(data) { + var calendars = new Backbone.Collection(); + + _.each(data.body, function(cal) { + if (cal.propStat.length < 1) { + return; + } + if (getResponseCodeFromHTTPResponse(cal.propStat[0].status) === 200) { + var properties = getCalendarData(cal.propStat[0].properties); + if (properties && properties.components.vevent) { + calendars.push(new Calendar(properties)); + } + } + }); + defer.resolve(calendars); + }, function() { + defer.reject(); + }); + + return defer.promise(); + } +}); diff --git a/js/templates/calendar.html b/js/templates/calendar.html new file mode 100644 index 000000000..c1e9bae6b --- /dev/null +++ b/js/templates/calendar.html @@ -0,0 +1 @@ +{{displayname}} \ No newline at end of file diff --git a/js/templates/message-attachment.html b/js/templates/message-attachment.html index 7fab1c1f1..2591d478b 100644 --- a/js/templates/message-attachment.html +++ b/js/templates/message-attachment.html @@ -8,4 +8,5 @@ {{/if}} - \ No newline at end of file + + \ No newline at end of file diff --git a/js/views/app.js b/js/views/app.js index 08a4ca699..cba1ee983 100644 --- a/js/views/app.js +++ b/js/views/app.js @@ -62,6 +62,10 @@ define(function(require) { window.addEventListener('resize', this.onWindowResize); + $(document).on('click', function(e) { + Radio.ui.trigger('document:click', e); + }); + // TODO: create marionette view and encapsulate events $(document).on('click', '#forward-button', function() { Radio.message.trigger('forward'); diff --git a/js/views/calendarspopoverview.js b/js/views/calendarspopoverview.js new file mode 100644 index 000000000..f2693cbb2 --- /dev/null +++ b/js/views/calendarspopoverview.js @@ -0,0 +1,30 @@ +/** + * @author Christoph Wurst + * + * ownCloud - Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +define(function(require) { + 'use strict'; + + var Marionette = require('marionette'); + var CalendarView = require('views/calendarview'); + + return Marionette.CollectionView.extend({ + childView: CalendarView, + tagName: 'ul' + }); +}); diff --git a/js/views/calendarview.js b/js/views/calendarview.js new file mode 100644 index 000000000..9b2e3ffee --- /dev/null +++ b/js/views/calendarview.js @@ -0,0 +1,31 @@ +/** + * @author Christoph Wurst + * + * ownCloud - Mail + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +define(function(require) { + 'use strict'; + + var Marionette = require('marionette'); + var Handlebars = require('handlebars'); + var CalendarTemplate = require('text!templates/calendar.html'); + + return Marionette.ItemView.extend({ + template: Handlebars.compile(CalendarTemplate), + tagName: 'li' + }); +}); diff --git a/js/views/messageattachment.js b/js/views/messageattachment.js index 4cb80998a..944c6b072 100644 --- a/js/views/messageattachment.js +++ b/js/views/messageattachment.js @@ -25,6 +25,7 @@ define(function(require) { var Marionette = require('marionette'); var Radio = require('radio'); var MessageController = require('controller/messagecontroller'); + var CalendarsPopoverView = require('views/calendarspopoverview'); var MessageAttachmentTemplate = require('text!templates/message-attachment.html'); /** @@ -35,15 +36,26 @@ define(function(require) { ui: { 'downloadButton': '.attachment-download', 'saveToCloudButton': '.attachment-save-to-cloud', - 'importCalendarEventButton': '.attachment-import.calendar' + 'importCalendarEventButton': '.attachment-import.calendar', + 'attachmentImportPopover': '.attachment-import-popover' }, events: { - 'click': '_onDownload', + 'click': '_onClick', 'click @ui.saveToCloudButton': '_onSaveToCloud', 'click @ui.importCalendarEventButton': '_onImportCalendarEvent' }, - _onDownload: function(e) { + initialize: function() { + this.listenTo(Radio.ui, 'document:click', this._closeImportPopover); + }, + _onClick: function(e) { if (!e.isDefaultPrevented()) { + var $target = $(e.target); + if ($target.hasClass('select-calendar')) { + var url = $target.data('calendar-url'); + alert(url); + return; + } + e.preventDefault(); window.location = this.model.get('downloadUrl'); } @@ -79,21 +91,34 @@ define(function(require) { .addClass('icon-loading-small'); var url = this.model.get('downloadUrl'); - var downloading = Radio.message.request('attachment:download', url); + var downloadingAttachment = Radio.message.request('attachment:download', url); + var fetchingCalendars = Radio.dav.request('calendars'); var _this = this; - $.when(downloading).done(function(data) { - console.log(data); - }); - $.when(downloading.fail(function() { + $.when(downloadingAttachment.fail(function() { Radio.ui.trigger('error:show', t('Error while downloading calendar event')); })); - $.when(downloading).always(function() { + $.when(fetchingCalendars, downloadingAttachment).always(function() { _this.ui.importCalendarEventButton .removeClass('icon-loading-small') .addClass('icon-add'); }); + $.when(fetchingCalendars, downloadingAttachment). + done(function(calendars, ics) { + _this.ui.attachmentImportPopover.removeClass('hidden'); + var calendarsView = new CalendarsPopoverView({ + collection: calendars + }); + calendarsView.render(); + _this.ui.attachmentImportPopover.html(calendarsView.$el); + }); + }, + _closeImportPopover: function(e) { + var $target = $(e.target); + if (this.$el.find($target).length === 0) { + this.ui.attachmentImportPopover.addClass('hidden'); + } } }); -- cgit v1.2.3 From b629b2a1c1f5bee6ab6308d3d21745c82d363c06 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 4 May 2016 17:40:17 +0200 Subject: upload ics to the selected calendar --- bower.json | 3 +- css/mail.css | 5 +-- js/require_config.js | 4 ++ js/service/davservice.js | 92 +++++++++++++++++++++++++++++++++++++++++++ js/templates/calendar.html | 2 +- js/views/messageattachment.js | 67 +++++++++++++++++++++---------- 6 files changed, 148 insertions(+), 25 deletions(-) diff --git a/bower.json b/bower.json index 1daeb3bf7..a5b87cd25 100644 --- a/bower.json +++ b/bower.json @@ -27,6 +27,7 @@ "text": "^2.0.14", "underscore": "~1.8.3", "davclient.js": "git@github.com:evert/davclient.js.git", - "es6-promise": "^3.2.1" + "es6-promise": "^3.2.1", + "ical.js": "^1.2.1" } } diff --git a/css/mail.css b/css/mail.css index cf97c2a2d..f7ef10dd1 100755 --- a/css/mail.css +++ b/css/mail.css @@ -680,12 +680,11 @@ input.submit-message, right: 79px; } .attachment-import-popover { - right: -17px; + right: 42px; top: 56px; } .attachment-import-popover::after { - left: 30px; - right: initial; + right: 32px; } /* show icon + text for Download all button as well as when there is only one attachment */ diff --git a/js/require_config.js b/js/require_config.js index d2a272f62..5499b87d7 100644 --- a/js/require_config.js +++ b/js/require_config.js @@ -25,12 +25,16 @@ domready: 'vendor/domReady/domReady', 'es6-promise': 'vendor/es6-promise/es6-promise.min', handlebars: 'vendor/handlebars/handlebars', + ical: 'vendor/ical.js/build/ical.min', marionette: 'vendor/backbone.marionette/lib/backbone.marionette', underscore: 'vendor/underscore/underscore', text: 'vendor/text/text' }, davclient: { exports: 'dav' + }, + ical: { + exports: 'ICAL' } }); diff --git a/js/service/davservice.js b/js/service/davservice.js index edf83e7c9..e91dabb70 100644 --- a/js/service/davservice.js +++ b/js/service/davservice.js @@ -24,11 +24,13 @@ define(function(require) { var $ = require('jquery'); var Backbone = require('backbone'); var dav = require('davclient'); + var ical = require('ical'); var OC = require('OC'); var Radio = require('radio'); var Calendar = require('models/dav/calendar'); Radio.dav.reply('calendars', getUserCalendars); + Radio.dav.reply('calendar:import', importCalendarEvent); var client = new dav.Client({ baseUrl: OC.linkToRemote('dav/calendars'), @@ -94,6 +96,7 @@ define(function(require) { if (getResponseCodeFromHTTPResponse(cal.propStat[0].status) === 200) { var properties = getCalendarData(cal.propStat[0].properties); if (properties && properties.components.vevent) { + properties.url = cal.href; calendars.push(new Calendar(properties)); } } @@ -105,4 +108,93 @@ define(function(require) { return defer.promise(); } + + function getRandomString() { + var str = ''; + for (var i = 0; i < 7; i++) { + str += Math.random().toString(36).substring(7); + } + return str; + } + + function createICalElement() { + var root = new ical.Component(['vcalendar', [], []]); + + root.updatePropertyWithValue('prodid', '-//ownCloud Mail'); + + return root; + } + + function splitCalendar(data) { + var timezones = []; + var allObjects = {}; + var jCal = ical.parse(data); + var components = new ical.Component(jCal); + + var vtimezones = components.getAllSubcomponents('vtimezone'); + _.each(vtimezones, function(vtimezone) { + timezones.push(vtimezone); + }); + + var componentNames = ['vevent', 'vjournal', 'vtodo']; + _.each(componentNames, function(componentName) { + var vobjects = components.getAllSubcomponents(componentName); + allObjects[componentName] = {}; + + _.each(vobjects, function(vobject) { + var uid = vobject.getFirstPropertyValue('uid'); + allObjects[componentName][uid] = allObjects[componentName][uid] || []; + allObjects[componentName][uid].push(vobject); + }); + }); + + var split = []; + _.each(componentNames, function(componentName) { + split[componentName] = []; + _.each(allObjects[componentName], function(objects) { + var component = createICalElement(); + _.each(timezones, function(timezone) { + component.addSubcomponent(timezone); + }); + _.each(objects, function(object) { + component.addSubcomponent(object); + }); + split[componentName].push(component.toString()); + }); + }); + + return { + name: components.getFirstPropertyValue('x-wr-calname'), + color: components.getFirstPropertyValue('x-apple-calendar-color'), + split: split + }; + } + + function importCalendarEvent(url, data) { + var defer = $.Deferred(); + var xhrs = []; + + var file = splitCalendar(data); + + var componentNames = ['vevent', 'vjournal', 'vtodo']; + _.each(componentNames, function(componentName) { + _.each(file.split[componentName], function(component) { + xhrs.push($.ajax({ + url: url + getRandomString(), + method: 'PUT', + contentType: 'text/calendar; charset=utf-8', + data: component, + error: function() { + defer.reject(); + } + })); + }); + }); + + $.when.apply($, xhrs).done(function() { + defer.resolve(); + }); + + return defer.promise(); + } }); diff --git a/js/templates/calendar.html b/js/templates/calendar.html index c1e9bae6b..b10bf7698 100644 --- a/js/templates/calendar.html +++ b/js/templates/calendar.html @@ -1 +1 @@ -{{displayname}} \ No newline at end of file +{{displayname}} \ No newline at end of file diff --git a/js/views/messageattachment.js b/js/views/messageattachment.js index 944c6b072..1c8992274 100644 --- a/js/views/messageattachment.js +++ b/js/views/messageattachment.js @@ -52,7 +52,7 @@ define(function(require) { var $target = $(e.target); if ($target.hasClass('select-calendar')) { var url = $target.data('calendar-url'); - alert(url); + this._uploadToCalendar(url); return; } @@ -72,40 +72,28 @@ define(function(require) { // Loading feedback this.ui.saveToCloudButton.removeClass('icon-folder') - .addClass('icon-loading-small') - .prop('disabled', true); + .addClass('icon-loading-small') + .prop('disabled', true); var _this = this; $.when(saving).always(function() { // Remove loading feedback again _this.ui.saveToCloudButton.addClass('icon-folder') - .removeClass('icon-loading-small') - .prop('disabled', false); + .removeClass('icon-loading-small') + .prop('disabled', false); }); }, _onImportCalendarEvent: function(e) { e.preventDefault(); this.ui.importCalendarEventButton - .removeClass('icon-add') - .addClass('icon-loading-small'); + .removeClass('icon-add') + .addClass('icon-loading-small'); - var url = this.model.get('downloadUrl'); - var downloadingAttachment = Radio.message.request('attachment:download', url); var fetchingCalendars = Radio.dav.request('calendars'); var _this = this; - $.when(downloadingAttachment.fail(function() { - Radio.ui.trigger('error:show', t('Error while downloading calendar event')); - })); - $.when(fetchingCalendars, downloadingAttachment).always(function() { - _this.ui.importCalendarEventButton - .removeClass('icon-loading-small') - .addClass('icon-add'); - }); - - $.when(fetchingCalendars, downloadingAttachment). - done(function(calendars, ics) { + $.when(fetchingCalendars).done(function(calendars) { _this.ui.attachmentImportPopover.removeClass('hidden'); var calendarsView = new CalendarsPopoverView({ collection: calendars @@ -113,8 +101,47 @@ define(function(require) { calendarsView.render(); _this.ui.attachmentImportPopover.html(calendarsView.$el); }); + $.when(fetchingCalendars).always(function() { + _this.ui.importCalendarEventButton + .removeClass('icon-loading-small') + .addClass('icon-add'); + }); + }, + _uploadToCalendar: function(url) { + this._closeImportPopover(); + this.ui.importCalendarEventButton + .removeClass('icon-add') + .addClass('icon-loading-small'); + + var downloadUrl = this.model.get('downloadUrl'); + var downloadingAttachment = Radio.message.request('attachment:download', downloadUrl); + + var _this = this; + $.when(downloadingAttachment).done(function(content) { + + var importingCalendarEvent = Radio.dav.request('calendar:import', url, content); + + $.when(importingCalendarEvent).fail(function() { + Radio.ui.trigger('error:show', t('mail', 'Error while importing the calendar event')); + }); + $.when(importingCalendarEvent).always(function() { + _this.ui.importCalendarEventButton + .removeClass('icon-loading-small') + .addClass('icon-add'); + }); + }); + $.when(downloadingAttachment.fail(function() { + Radio.ui.trigger('error:show', t('mail', 'Error while downloading calendar event')); + _this.ui.importCalendarEventButton + .removeClass('icon-loading-small') + .addClass('icon-add'); + })); }, _closeImportPopover: function(e) { + if (_.isUndefined(e)) { + this.ui.attachmentImportPopover.addClass('hidden'); + return; + } var $target = $(e.target); if (this.$el.find($target).length === 0) { this.ui.attachmentImportPopover.addClass('hidden'); -- cgit v1.2.3 From aac7cc438fa534b5909422779114bdb445e340b9 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Wed, 4 May 2016 23:34:33 +0200 Subject: show import button only on 9.0+ --- js/app.js | 2 ++ js/templates/message-attachment.html | 2 ++ js/views/messageattachment.js | 5 +++++ lib/controller/pagecontroller.php | 3 +++ templates/index.php | 1 + tests/controller/pagecontrollertest.php | 9 +++++++-- 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/js/app.js b/js/app.js index 67371932a..c2b1e1616 100644 --- a/js/app.js +++ b/js/app.js @@ -81,6 +81,8 @@ define(function(require) { Mail = new Mail(); Mail.on('start', function() { + this.hasDavSupport = $('#has-dav-support').val() === '1'; + this.view = new AppView(); Cache.init(); diff --git a/js/templates/message-attachment.html b/js/templates/message-attachment.html index 2591d478b..6171ebae9 100644 --- a/js/templates/message-attachment.html +++ b/js/templates/message-attachment.html @@ -5,8 +5,10 @@ {{fileName}} ({{humanFileSize size}}) {{#if isCalendarEvent}} +{{#if hasDavSupport}} {{/if}} +{{/if}} \ No newline at end of file diff --git a/js/views/messageattachment.js b/js/views/messageattachment.js index 1c8992274..1aff1ae63 100644 --- a/js/views/messageattachment.js +++ b/js/views/messageattachment.js @@ -44,6 +44,11 @@ define(function(require) { 'click @ui.saveToCloudButton': '_onSaveToCloud', 'click @ui.importCalendarEventButton': '_onImportCalendarEvent' }, + templateHelpers: function() { + return { + hasDavSupport: require('app').hasDavSupport + }; + }, initialize: function() { this.listenTo(Radio.ui, 'document:click', this._closeImportPopover); }, diff --git a/lib/controller/pagecontroller.php b/lib/controller/pagecontroller.php index 0b5067184..d1081d700 100644 --- a/lib/controller/pagecontroller.php +++ b/lib/controller/pagecontroller.php @@ -77,11 +77,14 @@ class PageController extends Controller { * @return TemplateResponse renders the index page */ public function index() { + $coreVersion = $this->config->getSystemValue('version', '0.0.0'); + $hasDavSupport = (int) version_compare($coreVersion, '9.0.0', '>='); // TODO: remove DEBUG constant check once minimum oc // core version >= 8.2, see https://github.com/owncloud/core/pull/18510 $response = new TemplateResponse($this->appName, 'index', [ 'debug' => (defined('DEBUG') && DEBUG) || $this->config->getSystemValue('debug', false), 'app-version' => $this->config->getAppValue('mail', 'installed_version'), + 'has-dav-support' => $hasDavSupport, ]); // set csp rules for ownCloud 8.1 diff --git a/templates/index.php b/templates/index.php index ac0b2ed73..4c122ebe1 100644 --- a/templates/index.php +++ b/templates/index.php @@ -40,6 +40,7 @@ if ($_['debug']) { ?> + diff --git a/tests/controller/pagecontrollertest.php b/tests/controller/pagecontrollertest.php index 01fa3e1b0..21a2bf7a2 100644 --- a/tests/controller/pagecontrollertest.php +++ b/tests/controller/pagecontrollertest.php @@ -48,7 +48,11 @@ class PageControllerTest extends TestCase { } public function testIndex() { - $this->config->expects($this->once()) + $this->config->expects($this->at(0)) + ->method('getSystemValue') + ->with('version', '0.0.0') + ->will($this->returnValue('8.2.0')); + $this->config->expects($this->at(1)) ->method('getSystemValue') ->with('debug', false) ->will($this->returnValue(true)); @@ -59,7 +63,8 @@ class PageControllerTest extends TestCase { $expected = new TemplateResponse($this->appName, 'index', [ 'debug' => true, - 'app-version' => '1.2.3' + 'app-version' => '1.2.3', + 'has-dav-support' => 0, ]); // set csp rules for ownCloud 8.1 if (class_exists('OCP\AppFramework\Http\ContentSecurityPolicy')) { -- cgit v1.2.3 From 3c258e88576dbc18d1ed1456a9e1bf1053463b3e Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Fri, 6 May 2016 11:08:22 +0200 Subject: show only writable calendars --- css/mail.css | 2 +- js/service/davservice.js | 27 +++++++++++++++++++++++++-- js/views/messageattachment.js | 16 ++++++++++------ 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/css/mail.css b/css/mail.css index f7ef10dd1..898615f64 100755 --- a/css/mail.css +++ b/css/mail.css @@ -697,7 +697,7 @@ input.submit-message, } .attachment-name { display: inline-block; - width: calc(100% - 110px); + width: calc(100% - 148px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/js/service/davservice.js b/js/service/davservice.js index e91dabb70..a73dfcfa2 100644 --- a/js/service/davservice.js +++ b/js/service/davservice.js @@ -59,14 +59,37 @@ define(function(require) { return parseInt(t.split(' ')[1]); } + function getACLFromResponse(properties) { + var canWrite = false; + var acl = properties['{DAV:}acl']; + if (acl) { + for (var k = 0; k < acl.length; k++) { + var href = acl[k].getElementsByTagNameNS('DAV:', 'href'); + if (href.length === 0) { + continue; + } + href = href[0].textContent; + var writeNode = acl[k].getElementsByTagNameNS('DAV:', 'write'); + if (writeNode.length > 0) { + canWrite = true; + } + } + } + properties.canWrite = canWrite; + } + ; + function getCalendarData(properties) { + getACLFromResponse(properties); + var data = { displayname: properties['{DAV:}displayname'], color: properties['{http://apple.com/ns/ical/}calendar-color'], order: properties['{http://apple.com/ns/ical/}calendar-order'], components: { vevent: false - } + }, + writable: properties.canWrite }; var components = properties['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] || []; @@ -95,7 +118,7 @@ define(function(require) { } if (getResponseCodeFromHTTPResponse(cal.propStat[0].status) === 200) { var properties = getCalendarData(cal.propStat[0].properties); - if (properties && properties.components.vevent) { + if (properties && properties.components.vevent && properties.writable === true) { properties.url = cal.href; calendars.push(new Calendar(properties)); } diff --git a/js/views/messageattachment.js b/js/views/messageattachment.js index 1aff1ae63..33e12465f 100644 --- a/js/views/messageattachment.js +++ b/js/views/messageattachment.js @@ -99,12 +99,16 @@ define(function(require) { var _this = this; $.when(fetchingCalendars).done(function(calendars) { - _this.ui.attachmentImportPopover.removeClass('hidden'); - var calendarsView = new CalendarsPopoverView({ - collection: calendars - }); - calendarsView.render(); - _this.ui.attachmentImportPopover.html(calendarsView.$el); + if (calendars.length > 0) { + _this.ui.attachmentImportPopover.removeClass('hidden'); + var calendarsView = new CalendarsPopoverView({ + collection: calendars + }); + calendarsView.render(); + _this.ui.attachmentImportPopover.html(calendarsView.$el); + } else { + Radio.ui.trigger('error:show', t('mail', 'No writable calendars found')); + } }); $.when(fetchingCalendars).always(function() { _this.ui.importCalendarEventButton -- cgit v1.2.3 From 7037862097192f8d790e5c8fc3dfd166a9ee5074 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Fri, 6 May 2016 11:47:28 +0200 Subject: update changelog and readme --- CHANGELOG.md | 4 ++++ README.md | 3 +-- js/require_config.js | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45aa09960..dcdc8b1bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. ## 0.4.5 – UNRELEASED +### Added +- Ability to import ics attachments into the calendar + [#1473](https://github.com/owncloud/mail/pull/1473) @ChristophWurst + ### Fixed - Bring back menu toggle button for mobile [#1483](https://github.com/owncloud/mail/pull/1483) @ChristophWurst diff --git a/README.md b/README.md index 39014f106..8d7e6b88b 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## Why is this so awesome? -* :rocket: **Integration with other ownCloud apps!** Currently Contacts & Files – more to come. +* :rocket: **Integration with other ownCloud apps!** Currently Contacts, Calendar & Files – more to come. * :inbox_tray: **Multiple mail accounts!** Personal and company account? No problem, and a nice unified inbox. * :lock: **Send & receive encrypted emails!** Using the great [Mailvelope](https://mailvelope.com) browser extension. * :see_no_evil: **We’re not reinventing the wheel!** Based on the great [Horde](http://horde.org) libraries. @@ -23,7 +23,6 @@ And in the works for the [coming versions](https://github.com/owncloud/mail/mile * :books: [Proper grouping of message threads](https://github.com/owncloud/mail/issues/21) * :zap: [Caching to make everything faster](https://github.com/owncloud/mail/issues/480) * :paperclip: [Even better attachment support](https://github.com/owncloud/mail/issues/462) -* :date: [Calendar integration](https://github.com/owncloud/mail/issues/79) * :package: [Folder management & moving mails](https://github.com/owncloud/mail/issues/411) ## Installation diff --git a/js/require_config.js b/js/require_config.js index 5499b87d7..e9d3482b8 100644 --- a/js/require_config.js +++ b/js/require_config.js @@ -29,12 +29,14 @@ marionette: 'vendor/backbone.marionette/lib/backbone.marionette', underscore: 'vendor/underscore/underscore', text: 'vendor/text/text' - }, + }, + shim: { davclient: { exports: 'dav' }, ical: { exports: 'ICAL' + } } }); -- cgit v1.2.3 From 1876bb928c8734bde66756994ea420fe4211e7e4 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Fri, 6 May 2016 13:23:17 +0200 Subject: install davclient via https because travis fails to install via ssh --- bower.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bower.json b/bower.json index a5b87cd25..80d503cfa 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,7 @@ "requirejs": "2.2.0", "text": "^2.0.14", "underscore": "~1.8.3", - "davclient.js": "git@github.com:evert/davclient.js.git", + "davclient.js": "https://github.com/evert/davclient.js.git", "es6-promise": "^3.2.1", "ical.js": "^1.2.1" } -- cgit v1.2.3