Skip to content

Commit

Permalink
Added Nextcloud save to Calendar #569
Browse files Browse the repository at this point in the history
  • Loading branch information
the-djmaze committed Oct 25, 2022
1 parent e183764 commit 797c958
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 19 deletions.
1 change: 1 addition & 0 deletions plugins/nextcloud/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function Init() : void
$this->addJs('js/messagelist.js');

$this->addTemplate('templates/PopupsNextcloudFiles.html');
$this->addTemplate('templates/PopupsNextcloudCalendars.html');
}
}

Expand Down
2 changes: 1 addition & 1 deletion plugins/nextcloud/js/composer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
31 changes: 29 additions & 2 deletions plugins/nextcloud/js/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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) => {
Expand All @@ -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));
});
}
}
});

Expand All @@ -71,6 +93,11 @@
+ '<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>'));

// 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');
Expand Down
137 changes: 121 additions & 16 deletions plugins/nextcloud/js/webdav.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@

const
namespace = 'DAV:',
nsCalDAV = 'urn:ietf:params:xml:ns:caldav',

propfindBody = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
propfindFiles = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
<d:prop>
<d:resourcetype />
<oc:size />
<d:getcontentlength />
<d:resourcetype/>
<oc:size/>
<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:propfind>`,

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'));
}
Expand All @@ -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 => {
Expand All @@ -61,19 +73,19 @@ 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) {
continue;
}
} 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);
}
Expand Down Expand Up @@ -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([
Expand Down
2 changes: 2 additions & 0 deletions plugins/nextcloud/langs/en.ini
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit 797c958

Please sign in to comment.