mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-16 01:54:20 +08:00
Added Nextcloud save to Calendar #569
This commit is contained in:
parent
e183764e51
commit
797c95877e
5 changed files with 154 additions and 19 deletions
|
@ -30,6 +30,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$this->addJs('js/messagelist.js');
|
||||
|
||||
$this->addTemplate('templates/PopupsNextcloudFiles.html');
|
||||
$this->addTemplate('templates/PopupsNextcloudCalendars.html');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 @@
|
|||
+ '<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');
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
@ -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([
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue