mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-09-20 07:35:55 +08:00
Compare commits
4 commits
48acb9ca38
...
ed41d8e45f
Author | SHA1 | Date | |
---|---|---|---|
ed41d8e45f | |||
80f3331187 | |||
9a7ff9fe27 | |||
12b6ed3dbb |
19
dev/External/SquireUI.js
vendored
19
dev/External/SquireUI.js
vendored
|
@ -155,7 +155,7 @@ class SquireUI
|
|||
html: 'B',
|
||||
cmd: () => this.doAction('bold'),
|
||||
key: 'B',
|
||||
matches: 'B,STRONT'
|
||||
matches: 'B,STRONG'
|
||||
},
|
||||
italic: {
|
||||
html: 'I',
|
||||
|
@ -477,15 +477,22 @@ class SquireUI
|
|||
|
||||
// -----
|
||||
|
||||
squire.addEventListener('pathChange', e => {
|
||||
squire.addEventListener('pathChange', () => {
|
||||
|
||||
const squireRoot = squire.getRoot();
|
||||
let elm = e.detail.element;
|
||||
|
||||
let range = squire.getSelection(),
|
||||
collapsed = range.collapsed,
|
||||
elm = collapsed ? range.endContainer : range?.commonAncestorContainer;
|
||||
if (elm && !(elm instanceof Element)) {
|
||||
elm = elm.parentElement;
|
||||
}
|
||||
forEachObjectValue(actions, entries => {
|
||||
forEachObjectValue(entries, cfg => {
|
||||
// cfg.matches && cfg.input.classList.toggle('active', elm && elm.matches(cfg.matches));
|
||||
cfg.matches && cfg.input.classList.toggle('active', elm && elm.closestWithin(cfg.matches, squireRoot));
|
||||
// Check if selection has a matching parent or contains a matching element
|
||||
cfg.matches && cfg.input.classList.toggle('active', !!(elm && (
|
||||
(!collapsed && [...elm.querySelectorAll(cfg.matches)].some(node => range.intersectsNode(node)))
|
||||
|| elm.closestWithin(cfg.matches, squireRoot)
|
||||
)));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -47,7 +47,10 @@ class Memcache implements \MailSo\Cache\DriverInterface
|
|||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
{
|
||||
return $this->oMem ? $this->oMem->set($this->generateCachedKey($sKey), $sValue, 0, $this->iExpire) : false;
|
||||
if ($this->oMem instanceof \Memcache) {
|
||||
return $this->oMem->set($this->generateCachedKey($sKey), $sValue, 0, $this->iExpire);
|
||||
}
|
||||
return $this->oMem ? $this->oMem->set($this->generateCachedKey($sKey), $sValue, $this->iExpire) : false;
|
||||
}
|
||||
|
||||
public function Exists(string $sKey) : bool
|
||||
|
|
|
@ -4,8 +4,8 @@ class CacheMemcachePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Cache Memcache',
|
||||
VERSION = '2.36',
|
||||
RELEASE = '2024-03-22',
|
||||
VERSION = '2.37',
|
||||
RELEASE = '2024-09-15',
|
||||
REQUIRED = '2.36.0',
|
||||
CATEGORY = 'Cache',
|
||||
DESCRIPTION = 'Cache handler using PHP Memcache or PHP Memcached';
|
||||
|
|
|
@ -35,6 +35,7 @@ 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');
|
||||
|
|
|
@ -21,9 +21,18 @@
|
|||
|
||||
// https://github.com/nextcloud/calendar/issues/4684
|
||||
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>`));
|
||||
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.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();
|
||||
VEVENT && rl.nextcloud.selectCalendar()
|
||||
.then(href => href && rl.nextcloud.calendarPut(href, VEVENT));
|
||||
VEVENT = await view.handleUpdatedRecurrentEvents(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
|
||||
*/
|
||||
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);
|
||||
|
@ -126,7 +286,7 @@
|
|||
// fetch it and parse the VEVENT
|
||||
rl.fetch(ics.linkDownload())
|
||||
.then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response })))
|
||||
.then(text => {
|
||||
.then(async (text) => {
|
||||
let VEVENT,
|
||||
VALARM,
|
||||
multiple = ['ATTACH','ATTENDEE','CATEGORIES','COMMENT','CONTACT','EXDATE',
|
||||
|
@ -177,6 +337,158 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -186,3 +498,148 @@
|
|||
});
|
||||
|
||||
})(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) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
// Happens after showModal()
|
||||
beforeShow(fResolve) {
|
||||
beforeShow(fResolve, VEVENT) {
|
||||
this.select = '';
|
||||
this.fResolve = fResolve;
|
||||
this.VEVENT = VEVENT
|
||||
this.tree.innerHTML = '';
|
||||
davFetch('calendars', '/', {
|
||||
method: 'PROPFIND',
|
||||
|
@ -432,14 +449,15 @@ close() {}
|
|||
}
|
||||
|
||||
rl.nextcloud = {
|
||||
selectCalendar: () =>
|
||||
selectCalendar: (VEVENT) =>
|
||||
new Promise(resolve => {
|
||||
NextcloudCalendarsPopupView.showModal([
|
||||
href => resolve(href),
|
||||
VEVENT
|
||||
]);
|
||||
}),
|
||||
|
||||
calendarPut: (path, event) => {
|
||||
calendarPut: (path, event, callback) => {
|
||||
davFetch('calendars', path + '/' + event.UID + '.ics', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
|
@ -463,6 +481,8 @@ rl.nextcloud = {
|
|||
// response.text().then(text => console.error({status:response.status, body:text}));
|
||||
Promise.reject(new Error({ response }));
|
||||
}
|
||||
|
||||
callback && callback(response)
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -500,3 +520,27 @@ 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_()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,21 @@
|
|||
"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"
|
||||
"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>
|
100
vendors/squire/build/squire-raw.js
vendored
100
vendors/squire/build/squire-raw.js
vendored
|
@ -99,12 +99,8 @@
|
|||
children = props;
|
||||
props = null;
|
||||
}
|
||||
if (props) {
|
||||
setAttributes(el, props);
|
||||
}
|
||||
if (children) {
|
||||
children.forEach((node) => el.append(node));
|
||||
}
|
||||
setAttributes(el, props);
|
||||
children && el.append(...children);
|
||||
return el;
|
||||
};
|
||||
var areAlike = (node, node2) => {
|
||||
|
@ -191,8 +187,7 @@
|
|||
}
|
||||
};
|
||||
var getClosest = (node, root, selector) => {
|
||||
var _a;
|
||||
node = (_a = node && !node.closest ? node.parentElement : node) == null ? void 0 : _a.closest(selector);
|
||||
node = (node && !node.closest ? node.parentElement : node)?.closest(selector);
|
||||
return node && root.contains(node) ? node : null;
|
||||
};
|
||||
var setAttributes = (node, props) => {
|
||||
|
@ -256,17 +251,13 @@
|
|||
|
||||
// source/range/Boundaries.ts
|
||||
var START_TO_START = 0;
|
||||
var START_TO_END = 1;
|
||||
var END_TO_END = 2;
|
||||
var END_TO_START = 3;
|
||||
var isNodeContainedInRange = (range, node, partial) => {
|
||||
const nodeRange = document.createRange();
|
||||
nodeRange.selectNode(node);
|
||||
if (partial) {
|
||||
const nodeEndBeforeStart = range.compareBoundaryPoints(END_TO_START, nodeRange) > -1;
|
||||
const nodeStartAfterEnd = range.compareBoundaryPoints(START_TO_END, nodeRange) < 1;
|
||||
return !nodeEndBeforeStart && !nodeStartAfterEnd;
|
||||
return range.intersectsNode(node);
|
||||
} else {
|
||||
const nodeRange = document.createRange();
|
||||
nodeRange.selectNode(node);
|
||||
const nodeStartAfterStart = range.compareBoundaryPoints(START_TO_START, nodeRange) < 1;
|
||||
const nodeEndBeforeEnd = range.compareBoundaryPoints(END_TO_END, nodeRange) > -1;
|
||||
return nodeStartAfterStart && nodeEndBeforeEnd;
|
||||
|
@ -744,7 +735,7 @@
|
|||
}
|
||||
};
|
||||
var allowedBlock = /^(?:A(?:DDRESS|RTICLE|SIDE|UDIO)|BLOCKQUOTE|CAPTION|D(?:[DLT]|IV)|F(?:IGURE|IGCAPTION|OOTER)|H[1-6]|HEADER|L(?:ABEL|EGEND|I)|O(?:L|UTPUT)|P(?:RE)?|SECTION|T(?:ABLE|BODY|D|FOOT|H|HEAD|R)|COL(?:GROUP)?|UL)$/;
|
||||
var blacklist = /^(?:HEAD|META|STYLE)/;
|
||||
var blacklist = /* @__PURE__ */ new Set(["HEAD", "META", "STYLE"]);
|
||||
var cleanTree = (node, config, preserveWS) => {
|
||||
const children = node.childNodes;
|
||||
let nonInlineParent = node;
|
||||
|
@ -755,72 +746,25 @@
|
|||
nonInlineParent,
|
||||
SHOW_ELEMENT_OR_TEXT
|
||||
);
|
||||
for (let i = 0, l = children.length; i < l; i += 1) {
|
||||
let i = children.length;
|
||||
while (i--) {
|
||||
let child = children[i];
|
||||
const nodeName = child.nodeName;
|
||||
const rewriter = stylesRewriters[nodeName];
|
||||
if (child instanceof HTMLElement) {
|
||||
const childLength = child.childNodes.length;
|
||||
if (rewriter) {
|
||||
child = rewriter(child, node, config);
|
||||
} else if (blacklist.test(nodeName)) {
|
||||
node.removeChild(child);
|
||||
i -= 1;
|
||||
l -= 1;
|
||||
if (stylesRewriters[nodeName]) {
|
||||
child = stylesRewriters[nodeName](child, node, config);
|
||||
} else if (blacklist.has(nodeName)) {
|
||||
child.remove();
|
||||
continue;
|
||||
} else if (!allowedBlock.test(nodeName) && !isInline(child)) {
|
||||
i -= 1;
|
||||
l += childLength - 1;
|
||||
node.replaceChild(empty(child), child);
|
||||
i += childLength;
|
||||
replaceWith(child, empty(child));
|
||||
continue;
|
||||
}
|
||||
if (childLength) {
|
||||
cleanTree(child, config, preserveWS || nodeName === "PRE");
|
||||
}
|
||||
} else {
|
||||
if (child instanceof Text) {
|
||||
let data = child.data;
|
||||
const startsWithWS = !notWS.test(data.charAt(0));
|
||||
const endsWithWS = !notWS.test(data.charAt(data.length - 1));
|
||||
if (preserveWS || !startsWithWS && !endsWithWS) {
|
||||
continue;
|
||||
}
|
||||
if (startsWithWS) {
|
||||
walker.currentNode = child;
|
||||
let sibling;
|
||||
while (sibling = walker.previousPONode()) {
|
||||
if (sibling.nodeName === "IMG" || sibling instanceof Text && notWS.test(sibling.data)) {
|
||||
break;
|
||||
}
|
||||
if (!isInline(sibling)) {
|
||||
sibling = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data = data.replace(/^[ \t\r\n]+/g, sibling ? " " : "");
|
||||
}
|
||||
if (endsWithWS) {
|
||||
walker.currentNode = child;
|
||||
let sibling;
|
||||
while (sibling = walker.nextNode()) {
|
||||
if (sibling.nodeName === "IMG" || sibling instanceof Text && notWS.test(sibling.data)) {
|
||||
break;
|
||||
}
|
||||
if (!isInline(sibling)) {
|
||||
sibling = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data = data.replace(/[ \t\r\n]+$/g, sibling ? " " : "");
|
||||
}
|
||||
if (data) {
|
||||
child.data = data;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
node.removeChild(child);
|
||||
i -= 1;
|
||||
l -= 1;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
|
@ -1316,7 +1260,7 @@
|
|||
const startBlock = getStartBlockOfRange(range, root);
|
||||
const endBlock = getEndBlockOfRange(range, root);
|
||||
let copyRoot = root;
|
||||
if (startBlock === endBlock && (startBlock == null ? void 0 : startBlock.contains(range.commonAncestorContainer))) {
|
||||
if (startBlock === endBlock && startBlock?.contains(range.commonAncestorContainer)) {
|
||||
copyRoot = startBlock;
|
||||
}
|
||||
let contents;
|
||||
|
@ -1714,7 +1658,6 @@
|
|||
|
||||
// source/keyboard/Space.ts
|
||||
var Space = (self, event, range) => {
|
||||
var _a;
|
||||
let node;
|
||||
const root = self._root;
|
||||
self._recordUndoState(range);
|
||||
|
@ -1727,7 +1670,7 @@
|
|||
} else if (rangeDoesEndAtBlockBoundary(range, root)) {
|
||||
const block = getStartBlockOfRange(range, root);
|
||||
if (block && block.nodeName !== "PRE") {
|
||||
const text = (_a = block.textContent) == null ? void 0 : _a.trimEnd().replace(ZWS, "");
|
||||
const text = block.textContent?.trimEnd().replace(ZWS, "");
|
||||
if (text === "*" || text === "1.") {
|
||||
event.preventDefault();
|
||||
self.insertPlainText(" ", false);
|
||||
|
@ -2380,10 +2323,7 @@
|
|||
range = null;
|
||||
}
|
||||
}
|
||||
if (!range) {
|
||||
range = createRange(root.firstElementChild || root, 0);
|
||||
}
|
||||
return range;
|
||||
return range || createRange(root.firstElementChild || root, 0);
|
||||
}
|
||||
setSelection(range) {
|
||||
this._lastSelection = range;
|
||||
|
@ -3823,8 +3763,8 @@
|
|||
}
|
||||
setAttribute(name, value) {
|
||||
let range = this.getSelection();
|
||||
let start = (range == null ? void 0 : range.startContainer) || {};
|
||||
let end = (range == null ? void 0 : range.endContainer) || {};
|
||||
let start = range?.startContainer || {};
|
||||
let end = range?.endContainer || {};
|
||||
if ("dir" == name || start instanceof Text && 0 === range.startOffset && start === end && end.length === range.endOffset) {
|
||||
this._recordUndoState(range);
|
||||
setAttributes(start.parentNode, { [name]: value });
|
||||
|
|
Loading…
Reference in a new issue