diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index 1f9f8b5e6..13256a3c1 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -35,7 +35,6 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin $this->addTemplate('templates/PopupsNextcloudFiles.html'); $this->addTemplate('templates/PopupsNextcloudCalendars.html'); - $this->addTemplate('templates/PopupsNextcloudInvites.html'); // $this->addHook('login.credentials.step-2', 'loginCredentials2'); // $this->addHook('login.credentials', 'loginCredentials'); diff --git a/plugins/nextcloud/js/message.js b/plugins/nextcloud/js/message.js index bab6793a4..f5154b745 100644 --- a/plugins/nextcloud/js/message.js +++ b/plugins/nextcloud/js/message.js @@ -21,18 +21,9 @@ // https://github.com/nextcloud/calendar/issues/4684 if (cfg.CalDAV) { - attachmentsControls.append(Element.fromHTML(` + attachmentsControls.append(Element.fromHTML(` `)); - attachmentsControls.append(Element.fromHTML(` - - `)) - attachmentsControls.append(Element.fromHTML(` - - `)) - attachmentsControls.append(Element.fromHTML(` - - `)) } } /* @@ -116,169 +107,18 @@ }; view.nextcloudICS = ko.observable(null); - view.nextcloudICSOldInvitation = ko.observable(null); - view.nextcloudICSNewInvitation = ko.observable(null); - view.nextcloudICSLastInvitation = ko.observable(null); - view.nextcloudICSShow = ko.observable(null); - - view.nextcloudICSCalendar = ko.observable(null); - view.filteredEventsUrls = ko.observable([]); - - view.nextcloudSaveICS = async () => { + view.nextcloudSaveICS = () => { let VEVENT = view.nextcloudICS(); - VEVENT = await view.handleUpdatedRecurrentEvents(VEVENT) - VEVENT && rl.nextcloud.selectCalendar(VEVENT) + VEVENT && rl.nextcloud.selectCalendar() + .then(href => href && rl.nextcloud.calendarPut(href, VEVENT)); } - - view.handleUpdatedRecurrentEvents = async (VEVENT) => { - - const uid = VEVENT.UID - - let makePUTRequest = false - - const filteredEventUrls = view.filteredEventUrls - - if (filteredEventUrls.length > 1) { - // console.warn('filteredEventUrls.length > 1') - } - else if (filteredEventUrls.length == 1) { - const eventUrl = filteredEventUrls[0] - const eventText = await fetchEvent(eventUrl) - - // don't do anything for equal cards - if (VEVENT.rawText == eventText) { - return VEVENT - } - - const newVeventsText = extractVEVENTs(VEVENT.rawText) - const oldVeventsText = extractVEVENTs(eventText) - - // if there's only one event in the old card, it can't be an edit of the exception card - if (oldVeventsText.length == 1) { - const newRecurrenceId = extractProperties('RECURRENCE-ID', newVeventsText[0]) - - // if there's a property RECURRENCE-ID in the new card, it's an recurrence exception event - if (newRecurrenceId.length > 0) { - const updatedVEVENT = { - 'SUMMARY' : VEVENT.SUMMARY, - 'UID' : VEVENT.UID, - 'rawText' : mergeEventTexts(eventText, newVeventsText[0]) - } - VEVENT = updatedVEVENT - makePUTRequest = true - } - else { - return VEVENT - } - } - // if there's more than one event in the old card, it's possible to be an inclusion of a new exception card - // or update of old exception card - else { - let recurrenceIdMatch = false - - let isUpdate = false - const updateData = { - 'oldEventIndex': null, - 'newEventIndex': null, - } - - // check if it's an update - for (let i = 0; i < oldVeventsText.length; i++) { - let oldVeventText = oldVeventsText[i] - - for (let j = 0; j < newVeventsText.length; j++) { - let newVeventText = newVeventsText[j] - - let oldRecurrenceId = extractProperties('RECURRENCE-ID', oldVeventText) - let newRecurrenceId = extractProperties('RECURRENCE-ID', newVeventText) - let oldSequence = extractProperties('SEQUENCE', oldVeventText) - let newSequence = extractProperties('SEQUENCE', newVeventText) - - if (oldRecurrenceId.length == 0 || newRecurrenceId.length == 0 || oldSequence.length == 0 || newSequence.length == 0) { - continue - } - - if (oldRecurrenceId[0] == newRecurrenceId[0]) { - if (newSequence[0] > oldSequence[0]) { - isUpdate = true - - updateData.oldEventIndex = i - updateData.newEventIndex = j - - i = oldVeventsText.length - j = newVeventsText.length - } - } - } - } - - // if it's an update... - if (isUpdate) { - // substitute old event text for new event text - const oldEventStart = eventText.indexOf(oldVeventsText[updateData.oldEventIndex]) - const oldEventEnd = oldEventStart + oldVeventsText[updateData.oldEventIndex].length - - const newEvent = eventText.substring(0, oldEventStart) + newVeventsText[updateData.newEventIndex] + eventText.substring(oldEventEnd) - - const updatedVEVENT = { - 'SUMMARY' : VEVENT.SUMMARY, - 'UID': VEVENT.UID, - 'rawText' : newEvent - } - VEVENT = updatedVEVENT - makePUTRequest = true - } - // if it's not an update, it's an inclusion, as there's no match of RECURRENCE-ID - else { - const updatedVEVENT = { - 'SUMMARY' : VEVENT.SUMMARY, - 'UID' : VEVENT.UID, - 'rawText' : mergeEventTexts(eventText, newVeventsText[0]) - } - VEVENT = updatedVEVENT - makePUTRequest = true - } - } - } - - if (makePUTRequest) { - let href = "/" + (filteredEventUrls[0].split('/').slice(5, -1)[0]) - - rl.nextcloud.calendarPut(href, VEVENT, (response) => { - if (response.status != 201 && response.status != 204) { - InvitesPopupView.showModal([ - rl.i18n('NEXTCLOUD/EVENT_UPDATE_FAILURE_TITLE'), - rl.i18n('NEXTCLOUD/EVENT_UPDATE_FAILURE_BODY', {eventName: VEVENT.SUMMARY}) - ]) - return - } - InvitesPopupView.showModal([ - rl.i18n('NEXTCLOUD/EVENT_UPDATED_TITLE'), - rl.i18n('NEXTCLOUD/EVENT_UPDATED_BODY', {eventName: VEVENT.SUMMARY}) - ]) - }) - - return null - } - - return VEVENT - } - - - /** * TODO */ view.message.subscribe(msg => { view.nextcloudICS(null); - view.nextcloudICSOldInvitation(null); - view.nextcloudICSNewInvitation(null); - view.nextcloudICSLastInvitation(null); - view.nextcloudICSShow(view.nextcloudICS()) - - if (msg && cfg.CalDAV) { // let ics = msg.attachments.find(attachment => 'application/ics' == attachment.mimeType); let ics = msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType); @@ -286,7 +126,7 @@ // fetch it and parse the VEVENT rl.fetch(ics.linkDownload()) .then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response }))) - .then(async (text) => { + .then(text => { let VEVENT, VALARM, multiple = ['ATTACH','ATTENDEE','CATEGORIES','COMMENT','CONTACT','EXDATE', @@ -337,158 +177,6 @@ shouldReply: VEVENT.shouldReply() }); view.nextcloudICS(VEVENT); - view.nextcloudICSShow(true); - - - // try to get calendars, save - const calendarUrls = await fetchCalendarUrls() - - const filteredEventUrls = [] - for (let i = 0; i < calendarUrls.length; i++) { - let calendarUrl = calendarUrls[i] - - const skipCalendars = ['/inbox/', '/outbox/', '/trashbin/'] - let skip = false - for (let j = 0; j < skipCalendars.length; j++) { - if (calendarUrl.includes(skipCalendars[j])) { - skip = true - break - } - } - if (skip) { - continue - } - - // try to get event - const eventUrls = await fetchEventUrl(calendarUrl, VEVENT.UID) - - if (eventUrls.length == 0) { - continue - } - - eventUrls.forEach((url) => { - filteredEventUrls.push(url) - }) - } - view.filteredEventUrls = filteredEventUrls - - // if there's none, save in view.nextcloudICS - if (filteredEventUrls.length == 0) { - view.nextcloudICS(VEVENT); - view.nextcloudICSShow(true) - } - // if there's some... - else { - const savedEvent = await fetchEvent(filteredEventUrls[0]) - - const newVeventsText = extractVEVENTs(VEVENT.rawText) - const oldVeventsText = extractVEVENTs(savedEvent) - - // if there's more than one event in the old card, it's possible to be inclusion of new exception card - // or updated of old exception card - if (oldVeventsText.length > 1) { - let recurrenceIdMatch = false - - let oldNewestCreated = null - let newNewestCreated = null - - // check if it's an update - for (let i = 0; i < oldVeventsText.length; i++) { - let oldVeventText = oldVeventsText[i] - - for (let j = 0; j < newVeventsText.length; j++) { - let newVeventText = newVeventsText[j] - - let oldRecurrenceId = extractProperties('RECURRENCE-ID', oldVeventText) - let newRecurrenceId = extractProperties('RECURRENCE-ID', newVeventText) - let oldSequence = extractProperties('SEQUENCE', oldVeventText) - let newSequence = extractProperties('SEQUENCE', newVeventText) - - if (newRecurrenceId.length == 0 && oldRecurrenceId.length == 1) { - view.nextcloudICSShow(false) - view.nextcloudICSOldInvitation(true) - } - - if (oldRecurrenceId.length > 0 && newRecurrenceId.length > 0 && oldSequence.length > 0 && newSequence.length > 0) { - if (oldRecurrenceId[0] == newRecurrenceId[0]) { - if (newSequence[0] < oldSequence[0]) { - view.nextcloudICSOldInvitation(true) - view.nextcloudICSShow(false) - } - else if (newSequence[0] == oldSequence[0]) { - view.nextcloudICSLastInvitation(true) - view.nextcloudICSShow(false) - } - else if (newSequence[0] > oldSequence[0]) { - view.nextcloudICSNewInvitation(true) - view.nextcloudICSShow(false) - } - - // exit for loops - j = newVeventsText.length - i = oldVeventsText.length - } - } - - let oldCreated = extractProperties('CREATED', oldVeventText) - let newCreated = extractProperties('CREATED', newVeventText) - - if (oldCreated.length == 0 || newCreated.length == 0) { - continue - } - - const formattedOldDate = oldCreated[0].replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/, '$1-$2-$3T$4:$5:$6Z'); - const formattedNewDate = newCreated[0].replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/, '$1-$2-$3T$4:$5:$6Z'); - - const oldDate = new Date(formattedOldDate) - const newDate = new Date(formattedNewDate) - - if (oldNewestCreated == null || oldDate > oldNewestCreated) { - oldNewestCreated = oldDate - } - if (newNewestCreated == null || newDate > newNewestCreated) { - newNewestCreated = newDate - } - } - } - - if (newNewestCreated != null && oldNewestCreated != null) { - if (newNewestCreated < oldNewestCreated) { - view.nextcloudICSOldInvitation(true) - view.nextcloudICSShow(false) - } - else if (newNewestCreated == oldNewestCreated) { - view.nextcloudICSLastInvitation(true) - view.nextcloudICSShow(false) - } - } - } - else { - const oldLastModified = extractProperties('LAST-MODIFIED', oldVeventsText[0]) - const newLastModified = extractProperties('LAST-MODIFIED', newVeventsText[0]) - - if (oldLastModified.length == 1 && newLastModified.length == 1) { - const formattedOldDate = oldLastModified[0].replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/, '$1-$2-$3T$4:$5:$6Z'); - const formattedNewDate = newLastModified[0].replace(/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/, '$1-$2-$3T$4:$5:$6Z'); - - const oldDate = new Date(formattedOldDate) - const newDate = new Date(formattedNewDate) - - if (newDate > oldDate) { - view.nextcloudICSNewInvitation(true) - view.nextcloudICSShow(false) - } - else if (newDate < oldDate) { - view.nextcloudICSOldInvitation(true) - view.nextcloudICSShow(false) - } - else { - view.nextcloudICSLastInvitation(true) - view.nextcloudICSShow(false) - } - } - } - } } }); } @@ -498,148 +186,3 @@ }); })(window.rl); - - -async function fetchCalendarUrls () { - const username = OC.currentUser - const requestToken = OC.requestToken - - const url = '/remote.php/dav/calendars/' + username - const response = await fetch(url, { - 'method': 'PROPFIND', - 'headers': { - 'Depth': '1', - 'Content-Type': 'application/xml', - 'requesttoken' : requestToken - } - }) - - if (!response.ok) { - throw new Error('Error fetching calendars', response) - } - - const responseText = await response.text() - - const parser = new DOMParser() - const xmlDoc = parser.parseFromString(responseText, 'application/xml') - - const calendarUrls = [] - - const hrefElements = xmlDoc.getElementsByTagName('d:href') - for (let i = 1; i < hrefElements.length; i++) { - let calendarUrl = hrefElements[i].textContent.trim() - calendarUrls.push(calendarUrl) - } - - return calendarUrls -} - - -async function fetchEventUrl (calendarUrl, uid) { - const requestToken = OC.requestToken - - const xmlRequestBody = ` - - - - - - - - - ${uid} - - - - -` - - const response = await fetch(calendarUrl, { - 'method': 'REPORT', - 'headers': { - 'Content-Type': 'application/xml', - 'Depth': 1, - 'requesttoken': requestToken - }, - 'body': xmlRequestBody - }) - - const responseText = await response.text() - - const parser = new DOMParser() - const xmlDoc = parser.parseFromString(responseText, 'application/xml') - - const hrefElements = xmlDoc.getElementsByTagName('d:href') - const hrefValues = Array.from(hrefElements).map(element => element.textContent) - - return hrefValues -} - - -async function fetchEvent (eventUrl) { - const requestToken = OC.requestToken - const response = await fetch(eventUrl, { - 'method': 'GET', - 'headers': { - 'requesttoken': requestToken - } - }) - const responseText = await response.text() - - return responseText -} - - -function extractVEVENTs(text) { - let lastEndIndex = 0 - const vEvents = [] - - let textToSearch = "" - let beginIndex = 0 - let endIndex = 0 - let foundVevent = false - - while (true) { - textToSearch = text.substring(lastEndIndex) - - beginIndex = textToSearch.indexOf('BEGIN:VEVENT') - if (beginIndex == -1) { - break - } - endIndex = textToSearch.substring(beginIndex).indexOf('END:VEVENT') - if (endIndex == -1) { - break - } - endIndex += beginIndex - - lastEndIndex = lastEndIndex + endIndex + 'END:VEVENT'.length - - foundVevent = textToSearch.substring(beginIndex + 'BEGIN:VEVENT'.length, endIndex) - - vEvents.push(foundVevent) - } - - return vEvents -} - - -function extractProperties (property, text) { - const matches = text.match(`${property}.*`) - - if (matches == null) { - return [] - } - - const separatedMatches = matches.map((match) => { - return match.substring(property.length + 1) - }) - - return separatedMatches -} - - -function mergeEventTexts (oldEventText, newEventText) { - const appendIndex = oldEventText.indexOf('END:VEVENT') + 'END:VEVENT'.length - const updatedEventText = oldEventText.substring(0, appendIndex) + "\nBEGIN:VEVENT" + newEventText + "END:VEVENT" + oldEventText.substring(appendIndex) - return updatedEventText -} diff --git a/plugins/nextcloud/js/webdav.js b/plugins/nextcloud/js/webdav.js index 4f2e2cafd..24aa1f226 100644 --- a/plugins/nextcloud/js/webdav.js +++ b/plugins/nextcloud/js/webdav.js @@ -320,28 +320,12 @@ class NextcloudCalendarsPopupView extends rl.pluginPopupView { } onBuild(dom) { - let modalObj = this this.tree = dom.querySelector('#sm-nc-calendars'); this.tree.addEventListener('click', event => { let el = event.target; if (el.matches('button')) { this.select = el.href; - let VEVENT = this.VEVENT - - rl.nextcloud.calendarPut(this.select, this.VEVENT, (response) => { - if (response.status != 201 && response.status != 204) { - InvitesPopupView.showModal([ - rl.i18n('MESSAGE/EVENT_ADDITION_FAILURE_TITLE'), - rl.i18n('MESSAGE/EVENT_ADDITION_FAILURE_BODY', {eventName: VEVENT.SUMMARY}), - () => {modalObj.close()} - ]) - } - InvitesPopupView.showModal([ - rl.i18n('MESSAGE/EVENT_ADDED_TITLE'), - rl.i18n('MESSAGE/EVENT_ADDED_BODY', {eventName: VEVENT.SUMMARY}), - () => {modalObj.close()} - ]) - }) + this.close(); } }); } @@ -391,10 +375,9 @@ class NextcloudCalendarsPopupView extends rl.pluginPopupView { treeElement.appendChild(li); } // Happens after showModal() - beforeShow(fResolve, VEVENT) { + beforeShow(fResolve) { this.select = ''; this.fResolve = fResolve; - this.VEVENT = VEVENT this.tree.innerHTML = ''; davFetch('calendars', '/', { method: 'PROPFIND', @@ -449,15 +432,14 @@ close() {} } rl.nextcloud = { - selectCalendar: (VEVENT) => + selectCalendar: () => new Promise(resolve => { NextcloudCalendarsPopupView.showModal([ href => resolve(href), - VEVENT ]); }), - calendarPut: (path, event, callback) => { + calendarPut: (path, event) => { davFetch('calendars', path + '/' + event.UID + '.ics', { method: 'PUT', headers: { @@ -481,8 +463,6 @@ rl.nextcloud = { // response.text().then(text => console.error({status:response.status, body:text})); Promise.reject(new Error({ response })); } - - callback && callback(response) }); }, @@ -520,27 +500,3 @@ function getElementsInNamespaces(xmlDocument, tagName) { } return results; } - - -class InvitesPopupView extends rl.pluginPopupView { - constructor() { - super('NextcloudInvites') - } - - onBuild(dom) { - this.title = dom.querySelector('#sm-invites-popup-title') - this.body = dom.querySelector('#sm-invites-popup-body') - } - - beforeShow(title, body, onHide_) { - this.title.innerHTML = title - this.body.innerHTML = body - this.onHide_ = onHide_ - } - - onHide() { - if (this.onHide_) { - this.onHide_() - } - } -} diff --git a/plugins/nextcloud/langs/en.json b/plugins/nextcloud/langs/en.json index d7843c3b9..2c19d11c2 100644 --- a/plugins/nextcloud/langs/en.json +++ b/plugins/nextcloud/langs/en.json @@ -2,21 +2,13 @@ "NEXTCLOUD": { "SAVE_ATTACHMENTS": "Save in Nextcloud", "SAVE_EML": "Save as .eml in Nextcloud", + "SAVE_ICS": "Add to calendar", "SELECT_FOLDER": "Select folder", "SELECT_FILES": "Select file(s)", "ATTACH_FILES": "Attach Nextcloud files", + "SELECT_CALENDAR": "Select calendar", "FILE_ATTACH": "attach", "FILE_INTERNAL": "internal", - "FILE_PUBLIC": "public", - - "SELECT_CALENDAR": "Select calendar", - "SAVE_ICS": "Add to calendar", - "OLD_INVITATION": "Old invitation", - "UPDATE_ON_MY_CALENDAR": "Update invitation", - "LAST_INVITATION": "Last invitation", - "EVENT_UPDATE_FAILURE_TITLE": "Update failed", - "EVENT_UPDATE_FAILURE_BODY": "Why, i have no clue", - "EVENT_UPDATED_TITLE": "Updated", - "EVENT_UPDATED_BODY": "Why, i have no clue" + "FILE_PUBLIC": "public" } } diff --git a/plugins/nextcloud/templates/PopupsNextcloudInvites.html b/plugins/nextcloud/templates/PopupsNextcloudInvites.html deleted file mode 100644 index 6056fb2ed..000000000 --- a/plugins/nextcloud/templates/PopupsNextcloudInvites.html +++ /dev/null @@ -1,8 +0,0 @@ -
- × -

-
- -