mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-09-20 07:35:55 +08:00
Implemen #1737 including an i18n fix
This commit is contained in:
parent
80f3331187
commit
ed41d8e45f
|
@ -35,6 +35,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
|
|
||||||
$this->addTemplate('templates/PopupsNextcloudFiles.html');
|
$this->addTemplate('templates/PopupsNextcloudFiles.html');
|
||||||
$this->addTemplate('templates/PopupsNextcloudCalendars.html');
|
$this->addTemplate('templates/PopupsNextcloudCalendars.html');
|
||||||
|
$this->addTemplate('templates/PopupsNextcloudInvites.html');
|
||||||
|
|
||||||
// $this->addHook('login.credentials.step-2', 'loginCredentials2');
|
// $this->addHook('login.credentials.step-2', 'loginCredentials2');
|
||||||
// $this->addHook('login.credentials', 'loginCredentials');
|
// $this->addHook('login.credentials', 'loginCredentials');
|
||||||
|
|
|
@ -21,9 +21,18 @@
|
||||||
|
|
||||||
// https://github.com/nextcloud/calendar/issues/4684
|
// https://github.com/nextcloud/calendar/issues/4684
|
||||||
if (cfg.CalDAV) {
|
if (cfg.CalDAV) {
|
||||||
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICS" data-icon="📅">
|
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICSShow" data-icon="📅">
|
||||||
<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/SAVE_ICS"></span>
|
<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/SAVE_ICS"></span>
|
||||||
</span>`));
|
</span>`));
|
||||||
|
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICSOldInvitation" data-icon="📅">
|
||||||
|
<span data-i18n="NEXTCLOUD/OLD_INVITATION"></span>
|
||||||
|
</span>`))
|
||||||
|
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICSNewInvitation" data-icon="📅">
|
||||||
|
<span class="g-ui-link" data-bind="click: nextcloudSaveICS" data-i18n="NEXTCLOUD/UPDATE_ON_MY_CALENDAR"></span>
|
||||||
|
</span>`))
|
||||||
|
attachmentsControls.append(Element.fromHTML(`<span data-bind="visible: nextcloudICSLastInvitation" data-icon="📅">
|
||||||
|
<span data-i18n="NEXTCLOUD/LAST_INVITATION"></span>
|
||||||
|
</span>`))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -107,18 +116,169 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
view.nextcloudICS = ko.observable(null);
|
view.nextcloudICS = ko.observable(null);
|
||||||
|
view.nextcloudICSOldInvitation = ko.observable(null);
|
||||||
|
view.nextcloudICSNewInvitation = ko.observable(null);
|
||||||
|
view.nextcloudICSLastInvitation = ko.observable(null);
|
||||||
|
|
||||||
view.nextcloudSaveICS = () => {
|
view.nextcloudICSShow = ko.observable(null);
|
||||||
|
|
||||||
|
view.nextcloudICSCalendar = ko.observable(null);
|
||||||
|
view.filteredEventsUrls = ko.observable([]);
|
||||||
|
|
||||||
|
view.nextcloudSaveICS = async () => {
|
||||||
let VEVENT = view.nextcloudICS();
|
let VEVENT = view.nextcloudICS();
|
||||||
VEVENT && rl.nextcloud.selectCalendar()
|
VEVENT = await view.handleUpdatedRecurrentEvents(VEVENT)
|
||||||
.then(href => href && rl.nextcloud.calendarPut(href, VEVENT));
|
VEVENT && rl.nextcloud.selectCalendar(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
|
* TODO
|
||||||
*/
|
*/
|
||||||
view.message.subscribe(msg => {
|
view.message.subscribe(msg => {
|
||||||
view.nextcloudICS(null);
|
view.nextcloudICS(null);
|
||||||
|
view.nextcloudICSOldInvitation(null);
|
||||||
|
view.nextcloudICSNewInvitation(null);
|
||||||
|
view.nextcloudICSLastInvitation(null);
|
||||||
|
view.nextcloudICSShow(view.nextcloudICS())
|
||||||
|
|
||||||
|
|
||||||
if (msg && cfg.CalDAV) {
|
if (msg && cfg.CalDAV) {
|
||||||
// let ics = msg.attachments.find(attachment => 'application/ics' == attachment.mimeType);
|
// let ics = msg.attachments.find(attachment => 'application/ics' == attachment.mimeType);
|
||||||
let ics = msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType);
|
let ics = msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType);
|
||||||
|
@ -126,7 +286,7 @@
|
||||||
// fetch it and parse the VEVENT
|
// fetch it and parse the VEVENT
|
||||||
rl.fetch(ics.linkDownload())
|
rl.fetch(ics.linkDownload())
|
||||||
.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(async (text) => {
|
||||||
let VEVENT,
|
let VEVENT,
|
||||||
VALARM,
|
VALARM,
|
||||||
multiple = ['ATTACH','ATTENDEE','CATEGORIES','COMMENT','CONTACT','EXDATE',
|
multiple = ['ATTACH','ATTENDEE','CATEGORIES','COMMENT','CONTACT','EXDATE',
|
||||||
|
@ -177,6 +337,158 @@
|
||||||
shouldReply: VEVENT.shouldReply()
|
shouldReply: VEVENT.shouldReply()
|
||||||
});
|
});
|
||||||
view.nextcloudICS(VEVENT);
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -186,3 +498,148 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
})(window.rl);
|
})(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 = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav">
|
||||||
|
<C:prop>
|
||||||
|
<C:calendar-data/>
|
||||||
|
</C:prop>
|
||||||
|
<C:filter>
|
||||||
|
<C:comp-filter name="VCALENDAR">
|
||||||
|
<C:comp-filter name="VEVENT">
|
||||||
|
<C:prop-filter name="UID">
|
||||||
|
<C:text-match collation="i;unicode-casemap" match-type="equals">${uid}</C:text-match>
|
||||||
|
</C:prop-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:comp-filter>
|
||||||
|
</C:filter>
|
||||||
|
</C:calendar-query>`
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -320,12 +320,28 @@ class NextcloudCalendarsPopupView extends rl.pluginPopupView {
|
||||||
}
|
}
|
||||||
|
|
||||||
onBuild(dom) {
|
onBuild(dom) {
|
||||||
|
let modalObj = this
|
||||||
this.tree = dom.querySelector('#sm-nc-calendars');
|
this.tree = dom.querySelector('#sm-nc-calendars');
|
||||||
this.tree.addEventListener('click', event => {
|
this.tree.addEventListener('click', event => {
|
||||||
let el = event.target;
|
let el = event.target;
|
||||||
if (el.matches('button')) {
|
if (el.matches('button')) {
|
||||||
this.select = el.href;
|
this.select = el.href;
|
||||||
this.close();
|
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()}
|
||||||
|
])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -375,9 +391,10 @@ class NextcloudCalendarsPopupView extends rl.pluginPopupView {
|
||||||
treeElement.appendChild(li);
|
treeElement.appendChild(li);
|
||||||
}
|
}
|
||||||
// Happens after showModal()
|
// Happens after showModal()
|
||||||
beforeShow(fResolve) {
|
beforeShow(fResolve, VEVENT) {
|
||||||
this.select = '';
|
this.select = '';
|
||||||
this.fResolve = fResolve;
|
this.fResolve = fResolve;
|
||||||
|
this.VEVENT = VEVENT
|
||||||
this.tree.innerHTML = '';
|
this.tree.innerHTML = '';
|
||||||
davFetch('calendars', '/', {
|
davFetch('calendars', '/', {
|
||||||
method: 'PROPFIND',
|
method: 'PROPFIND',
|
||||||
|
@ -432,14 +449,15 @@ close() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.nextcloud = {
|
rl.nextcloud = {
|
||||||
selectCalendar: () =>
|
selectCalendar: (VEVENT) =>
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
NextcloudCalendarsPopupView.showModal([
|
NextcloudCalendarsPopupView.showModal([
|
||||||
href => resolve(href),
|
href => resolve(href),
|
||||||
|
VEVENT
|
||||||
]);
|
]);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
calendarPut: (path, event) => {
|
calendarPut: (path, event, callback) => {
|
||||||
davFetch('calendars', path + '/' + event.UID + '.ics', {
|
davFetch('calendars', path + '/' + event.UID + '.ics', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -463,6 +481,8 @@ rl.nextcloud = {
|
||||||
// response.text().then(text => console.error({status:response.status, body:text}));
|
// response.text().then(text => console.error({status:response.status, body:text}));
|
||||||
Promise.reject(new Error({ response }));
|
Promise.reject(new Error({ response }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback && callback(response)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -500,3 +520,27 @@ function getElementsInNamespaces(xmlDocument, tagName) {
|
||||||
}
|
}
|
||||||
return results;
|
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_()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,13 +2,21 @@
|
||||||
"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": "Add to 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",
|
|
||||||
"FILE_ATTACH": "attach",
|
"FILE_ATTACH": "attach",
|
||||||
"FILE_INTERNAL": "internal",
|
"FILE_INTERNAL": "internal",
|
||||||
"FILE_PUBLIC": "public"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
8
plugins/nextcloud/templates/PopupsNextcloudInvites.html
Normal file
8
plugins/nextcloud/templates/PopupsNextcloudInvites.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<header>
|
||||||
|
<a class="close" href="#" data-bind="click: close">×</a>
|
||||||
|
<h3 id="sm-invites-popup-title"></h3>
|
||||||
|
</header>
|
||||||
|
<div class="modal-body form-horizontal" id="sm-invites-popup-body">
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
</footer>
|
Loading…
Reference in a new issue