From 797c95877ea468a6e345db580f4910f4a08b695f Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 25 Oct 2022 16:08:28 +0200 Subject: [PATCH] Added Nextcloud save to Calendar #569 --- plugins/nextcloud/index.php | 1 + plugins/nextcloud/js/composer.js | 2 +- plugins/nextcloud/js/message.js | 31 ++++++- plugins/nextcloud/js/webdav.js | 137 +++++++++++++++++++++++++++---- plugins/nextcloud/langs/en.ini | 2 + 5 files changed, 154 insertions(+), 19 deletions(-) diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index ca0bf73507..d629bbffbf 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -30,6 +30,7 @@ public function Init() : void $this->addJs('js/messagelist.js'); $this->addTemplate('templates/PopupsNextcloudFiles.html'); + $this->addTemplate('templates/PopupsNextcloudCalendars.html'); } } diff --git a/plugins/nextcloud/js/composer.js b/plugins/nextcloud/js/composer.js index 26073b63fc..02d65f0210 100644 --- a/plugins/nextcloud/js/composer.js +++ b/plugins/nextcloud/js/composer.js @@ -5,7 +5,7 @@ if ('PopupsCompose' === e.detail.viewModelTemplateID) { let view = e.detail; view.nextcloudAttach = () => { - rl.ncFiles.selectFiles().then(files => { + rl.nextcloud.selectFiles().then(files => { files && files.forEach(file => { let attachment = view.addAttachmentHelper( Jua?.randomId(), diff --git a/plugins/nextcloud/js/message.js b/plugins/nextcloud/js/message.js index bc7353897e..8bde31d695 100644 --- a/plugins/nextcloud/js/message.js +++ b/plugins/nextcloud/js/message.js @@ -13,7 +13,7 @@ .filter(v => v); if (hashes.length) { view.saveNextcloudLoading(true); - rl.ncFiles.selectFolder().then(folder => { + rl.nextcloud.selectFolder().then(folder => { if (folder) { rl.fetchJSON('./?/Json/&q[]=/0/', {}, { Action: 'AttachmentsActions', @@ -41,7 +41,7 @@ }; view.nextcloudSaveMsg = () => { - rl.ncFiles.selectFolder().then(folder => { + rl.nextcloud.selectFolder().then(folder => { let msg = view.message(); folder && rl.pluginRemoteRequest( (iError, data) => { @@ -59,6 +59,28 @@ ); }); }; + + view.nextcloudICS = ko.computed(() => { + let msg = view.message(); + return msg + ? msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType) + : null; + }, {'pure':true}); + + view.nextcloudSaveICS = () => { + let attachment = view.nextcloudICS(); + attachment && rl.nextcloud.selectCalendar().then(href => { + console.dir({href: href}); + fetch(attachment.linkDownload(), { + mode: 'same-origin', + cache: 'no-cache', + redirect: 'error', + credentials: 'same-origin' + }) + .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) + .then(text => rl.nextcloud.calendarPut(href, text)); + }); + } } }); @@ -71,6 +93,11 @@ + '💾' + '' + '')); + + // https://github.com/nextcloud/calendar/issues/4684 +// attachmentsControls.append(Element.fromHTML('' +// + '' +// + '')); } const msgMenu = template.content.querySelector('#more-view-dropdown-id + menu'); diff --git a/plugins/nextcloud/js/webdav.js b/plugins/nextcloud/js/webdav.js index 722a7ce554..12a8aa5449 100644 --- a/plugins/nextcloud/js/webdav.js +++ b/plugins/nextcloud/js/webdav.js @@ -2,24 +2,34 @@ const namespace = 'DAV:', + nsCalDAV = 'urn:ietf:params:xml:ns:caldav', - propfindBody = ` - + propfindFiles = ` + - - - + + + + +`, + + propfindCal = ` + + + + + `, xmlParser = new DOMParser(), - pathRegex = /.*\/remote.php\/dav\/files\/[^/]+/g, + pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g, getDavElementsByTagName = (parent, localName) => parent.getElementsByTagNameNS(namespace, localName), getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), - davFetch = (path, options) => { + davFetch = (mode, path, options) => { if (!parent.OC.requestToken) { return Promise.reject(new Error('OC.requestToken missing')); } @@ -32,21 +42,23 @@ const headers: {} }, options); options.headers.requesttoken = parent.OC.requestToken; - return fetch(cfg.WebDAV + '/files/' + cfg.UID + path, options); + return fetch(cfg.WebDAV + '/' + mode + '/' + cfg.UID + path, options); }, - createDirectory = path => davFetch(path, { method: 'MKCOL' }), + davFetchFiles = (path, options) => davFetch('files', path, options), + + createDirectory = path => davFetchFiles(path, { method: 'MKCOL' }), fetchFiles = path => { if (!parent.OC.requestToken) { return Promise.reject(new Error('OC.requestToken missing')); } - return davFetch(path, { + return davFetchFiles(path, { method: 'PROPFIND', headers: { 'Content-Type': 'application/xml; charset=utf-8' }, - body: propfindBody + body: propfindFiles }) .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) .then(text => { @@ -61,10 +73,10 @@ const const e = responseList.item(i), elem = { - name: decodeURIComponent(getDavElementByTagName(e, 'href').innerHTML) + name: getDavElementByTagName(e, 'href').textContent .replace(pathRegex, '').replace(/\/$/, ''), isFile: false - } + }; if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { // skip current directory if (elem.name === path) { @@ -72,8 +84,8 @@ const } } else { elem.isFile = true; - elem.size = getDavElementByTagName(e, 'getcontentlength')?.innerHTML - || getElementByTagName(e, 'oc:size')?.innerHTML; + elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent + || getElementByTagName(e, 'oc:size')?.textContent; } elemList.push(elem); } @@ -202,7 +214,100 @@ close() {} */ } -rl.ncFiles = { +class NextcloudCalendarsPopupView extends rl.pluginPopupView { + constructor() { + super('NextcloudCalendars'); + } + + onBuild(dom) { + this.tree = dom.querySelector('#sm-nc-calendars'); + this.tree.addEventListener('click', event => { + let el = event.target; + if (el.matches('button')) { + this.select = el.href; + this.close(); + } + }); + } + + // Happens after showModal() + beforeShow(fResolve) { + this.select = ''; + this.fResolve = fResolve; + this.tree.innerHTML = ''; + davFetch('calendars', '/', { + method: 'PROPFIND', + headers: { + 'Content-Type': 'application/xml; charset=utf-8' + }, + body: propfindCal + }) + .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) + .then(text => { + const + responseList = getDavElementsByTagName( + xmlParser.parseFromString(text, 'application/xml').documentElement, + 'response' + ); + for (let i = 0; i < responseList.length; ++i) { + const e = responseList.item(i); + if (getDavElementByTagName(e, 'resourcetype').getElementsByTagNameNS(nsCalDAV, 'calendar').length) { +// && getDavElementsByTagName(getDavElementByTagName(e, 'current-user-privilege-set'), 'write').length) { + const li = document.createElement('li'), + btn = document.createElement('button'); + li.textContent = getDavElementByTagName(e, 'displayname').textContent; + btn.href = getDavElementByTagName(e, 'href').textContent + .replace(pathRegex, '').replace(/\/$/, ''); + btn.textContent = 'select'; + btn.className = 'button-vue'; + btn.style.marginLeft = '1em'; + li.append(btn); + this.tree.append(li); + } + } + }) + .catch(err => console.error(err)); + } + + onHide() { + this.fResolve(this.select); + } +/* +beforeShow() {} // Happens before showModal() +onShow() {} // Happens after showModal() +afterShow() {} // Happens after showModal() animation transitionend +onHide() {} // Happens before animation transitionend +afterHide() {} // Happens after animation transitionend +close() {} +*/ +} + +rl.nextcloud = { + selectCalendar: () => + new Promise(resolve => { + NextcloudCalendarsPopupView.showModal([ + href => resolve(href), + ]); + }), + + calendarPut: (path, event) => { + // Validation error in iCalendar: A calendar object on a CalDAV server MUST NOT have a METHOD property. + event = event.replace(/METHOD:.+\r?\n/i, ''); + + let m = event.match(/UID:(.+)/); + davFetch('calendars', path + '/' + m[1] + '.ics', { + method: 'PUT', + headers: { + 'Content-Type': 'text/calendar' + }, + body: event + }) + .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) + .then(text => { + console.dir({event_response:text}); + }); + }, + selectFolder: () => new Promise(resolve => { NextcloudFilesPopupView.showModal([ diff --git a/plugins/nextcloud/langs/en.ini b/plugins/nextcloud/langs/en.ini index 4a5c783fda..830b4ecabe 100644 --- a/plugins/nextcloud/langs/en.ini +++ b/plugins/nextcloud/langs/en.ini @@ -1,6 +1,8 @@ [NEXTCLOUD] SAVE_ATTACHMENTS = "Save in Nextcloud" SAVE_EML = "Save as .eml in Nextcloud" +SAVE_ICS = "Put in Calendar" SELECT_FOLDER = "Select folder" SELECT_FILES = "Select file(s)" ATTACH_FILES = "Attach Nextcloud files" +SELECT_CALENDAR = "Select calendar"