Added Nextcloud save to Calendar #569

This commit is contained in:
the-djmaze 2022-10-25 16:08:28 +02:00
parent e183764e51
commit 797c95877e
5 changed files with 154 additions and 19 deletions

View file

@ -30,6 +30,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addJs('js/messagelist.js'); $this->addJs('js/messagelist.js');
$this->addTemplate('templates/PopupsNextcloudFiles.html'); $this->addTemplate('templates/PopupsNextcloudFiles.html');
$this->addTemplate('templates/PopupsNextcloudCalendars.html');
} }
} }

View file

@ -5,7 +5,7 @@
if ('PopupsCompose' === e.detail.viewModelTemplateID) { if ('PopupsCompose' === e.detail.viewModelTemplateID) {
let view = e.detail; let view = e.detail;
view.nextcloudAttach = () => { view.nextcloudAttach = () => {
rl.ncFiles.selectFiles().then(files => { rl.nextcloud.selectFiles().then(files => {
files && files.forEach(file => { files && files.forEach(file => {
let attachment = view.addAttachmentHelper( let attachment = view.addAttachmentHelper(
Jua?.randomId(), Jua?.randomId(),

View file

@ -13,7 +13,7 @@
.filter(v => v); .filter(v => v);
if (hashes.length) { if (hashes.length) {
view.saveNextcloudLoading(true); view.saveNextcloudLoading(true);
rl.ncFiles.selectFolder().then(folder => { rl.nextcloud.selectFolder().then(folder => {
if (folder) { if (folder) {
rl.fetchJSON('./?/Json/&q[]=/0/', {}, { rl.fetchJSON('./?/Json/&q[]=/0/', {}, {
Action: 'AttachmentsActions', Action: 'AttachmentsActions',
@ -41,7 +41,7 @@
}; };
view.nextcloudSaveMsg = () => { view.nextcloudSaveMsg = () => {
rl.ncFiles.selectFolder().then(folder => { rl.nextcloud.selectFolder().then(folder => {
let msg = view.message(); let msg = view.message();
folder && rl.pluginRemoteRequest( folder && rl.pluginRemoteRequest(
(iError, data) => { (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 @@
+ '<i class="fontastic" data-bind="visible: !saveNextcloudError(), css: {\'icon-spinner\': saveNextcloudLoading()}">💾</i>' + '<i class="fontastic" data-bind="visible: !saveNextcloudError(), css: {\'icon-spinner\': saveNextcloudLoading()}">💾</i>'
+ '<span class="g-ui-link" data-bind="click: saveNextcloud" data-i18n="NEXTCLOUD/SAVE_ATTACHMENTS"></span>' + '<span class="g-ui-link" data-bind="click: saveNextcloud" data-i18n="NEXTCLOUD/SAVE_ATTACHMENTS"></span>'
+ '</span>')); + '</span>'));
// https://github.com/nextcloud/calendar/issues/4684
// attachmentsControls.append(Element.fromHTML('<span data-bind="visible: nextcloudICS" data-icon="📅">'
// + '<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/SAVE_ICS"></span>'
// + '</span>'));
} }
const msgMenu = template.content.querySelector('#more-view-dropdown-id + menu'); const msgMenu = template.content.querySelector('#more-view-dropdown-id + menu');

View file

@ -2,24 +2,34 @@
const const
namespace = 'DAV:', namespace = 'DAV:',
nsCalDAV = 'urn:ietf:params:xml:ns:caldav',
propfindBody = `<?xml version="1.0"?> propfindFiles = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns"> <d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop> <d:prop>
<d:resourcetype /> <d:resourcetype/>
<oc:size /> <oc:size/>
<d:getcontentlength /> <d:getcontentlength/>
</d:prop>
</d:propfind>`,
propfindCal = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:">
<d:prop>
<d:resourcetype/>
<d:current-user-privilege-set/>
<d:displayname/>
</d:prop> </d:prop>
</d:propfind>`, </d:propfind>`,
xmlParser = new DOMParser(), xmlParser = new DOMParser(),
pathRegex = /.*\/remote.php\/dav\/files\/[^/]+/g, pathRegex = /.*\/remote.php\/dav\/[^/]+\/[^/]+/g,
getDavElementsByTagName = (parent, localName) => parent.getElementsByTagNameNS(namespace, localName), getDavElementsByTagName = (parent, localName) => parent.getElementsByTagNameNS(namespace, localName),
getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0), getDavElementByTagName = (parent, localName) => getDavElementsByTagName(parent, localName)?.item(0),
getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0), getElementByTagName = (parent, localName) => +parent.getElementsByTagName(localName)?.item(0),
davFetch = (path, options) => { davFetch = (mode, path, options) => {
if (!parent.OC.requestToken) { if (!parent.OC.requestToken) {
return Promise.reject(new Error('OC.requestToken missing')); return Promise.reject(new Error('OC.requestToken missing'));
} }
@ -32,21 +42,23 @@ const
headers: {} headers: {}
}, options); }, options);
options.headers.requesttoken = parent.OC.requestToken; 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 => { fetchFiles = path => {
if (!parent.OC.requestToken) { if (!parent.OC.requestToken) {
return Promise.reject(new Error('OC.requestToken missing')); return Promise.reject(new Error('OC.requestToken missing'));
} }
return davFetch(path, { return davFetchFiles(path, {
method: 'PROPFIND', method: 'PROPFIND',
headers: { headers: {
'Content-Type': 'application/xml; charset=utf-8' 'Content-Type': 'application/xml; charset=utf-8'
}, },
body: propfindBody body: propfindFiles
}) })
.then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response })))
.then(text => { .then(text => {
@ -61,10 +73,10 @@ const
const const
e = responseList.item(i), e = responseList.item(i),
elem = { elem = {
name: decodeURIComponent(getDavElementByTagName(e, 'href').innerHTML) name: getDavElementByTagName(e, 'href').textContent
.replace(pathRegex, '').replace(/\/$/, ''), .replace(pathRegex, '').replace(/\/$/, ''),
isFile: false isFile: false
} };
if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) { if (getDavElementsByTagName(getDavElementByTagName(e, 'resourcetype'), 'collection').length) {
// skip current directory // skip current directory
if (elem.name === path) { if (elem.name === path) {
@ -72,8 +84,8 @@ const
} }
} else { } else {
elem.isFile = true; elem.isFile = true;
elem.size = getDavElementByTagName(e, 'getcontentlength')?.innerHTML elem.size = getDavElementByTagName(e, 'getcontentlength')?.textContent
|| getElementByTagName(e, 'oc:size')?.innerHTML; || getElementByTagName(e, 'oc:size')?.textContent;
} }
elemList.push(elem); 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: () => selectFolder: () =>
new Promise(resolve => { new Promise(resolve => {
NextcloudFilesPopupView.showModal([ NextcloudFilesPopupView.showModal([

View file

@ -1,6 +1,8 @@
[NEXTCLOUD] [NEXTCLOUD]
SAVE_ATTACHMENTS = "Save in Nextcloud" SAVE_ATTACHMENTS = "Save in Nextcloud"
SAVE_EML = "Save as .eml in Nextcloud" SAVE_EML = "Save as .eml in Nextcloud"
SAVE_ICS = "Put in Calendar"
SELECT_FOLDER = "Select folder" SELECT_FOLDER = "Select folder"
SELECT_FILES = "Select file(s)" SELECT_FILES = "Select file(s)"
ATTACH_FILES = "Attach Nextcloud files" ATTACH_FILES = "Attach Nextcloud files"
SELECT_CALENDAR = "Select calendar"