mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-12-27 09:36:09 +08:00
Added "View ICS" extension #1178
This commit is contained in:
parent
5251d85fef
commit
120873c7d6
3 changed files with 151 additions and 0 deletions
20
plugins/view-ics/LICENSE
Normal file
20
plugins/view-ics/LICENSE
Normal file
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 SnappyMail
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
18
plugins/view-ics/index.php
Normal file
18
plugins/view-ics/index.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
class ViewICSPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'View ICS',
|
||||
VERSION = '2.0',
|
||||
RELEASE = '2023-08-28',
|
||||
CATEGORY = 'Messages',
|
||||
DESCRIPTION = 'Display ICS attachment details',
|
||||
REQUIRED = '2.27.0';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
// $this->UseLangs(true);
|
||||
$this->addJs('message.js');
|
||||
}
|
||||
}
|
113
plugins/view-ics/message.js
Normal file
113
plugins/view-ics/message.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
(rl => {
|
||||
const templateId = 'MailMessageView';
|
||||
|
||||
addEventListener('rl-view-model.create', e => {
|
||||
if (templateId === e.detail.viewModelTemplateID) {
|
||||
|
||||
const
|
||||
template = document.getElementById(templateId),
|
||||
view = e.detail,
|
||||
attachmentsPlace = template.content.querySelector('.attachmentsPlace'),
|
||||
dateRegEx = /(TZID=(?<tz>[^:]+):)?(?<year>[0-9]{4})(?<month>[0-9]{2})(?<day>[0-9]{2})T(?<hour>[0-9]{2})(?<minute>[0-9]{2})(?<second>[0-9]{2})(?<utc>Z?)/;
|
||||
|
||||
attachmentsPlace.after(Element.fromHTML(`
|
||||
<details data-bind="if: viewICS, visible: viewICS">
|
||||
<summary data-icon="📅" data-bind="text: viewICS().SUMMARY"></summary>
|
||||
<table><tbody style="white-space:pre">
|
||||
<tr data-bind="visible: viewICS().ORGANIZER"><td>Organizer</td><td data-bind="text: viewICS().ORGANIZER"></td></tr>
|
||||
<tr><td>Start</td><td data-bind="text: viewICS().DTSTART"></td></tr>
|
||||
<tr><td>End</td><td data-bind="text: viewICS().DTEND"></td></tr>
|
||||
<!-- <tr><td>Transparency</td><td data-bind="text: viewICS().TRANSP"></td></tr>-->
|
||||
<tr data-bind="foreach: viewICS().ATTENDEE">
|
||||
<td></td><td data-bind="text: $data.replace(/;/g,';\\n')"></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</details>`));
|
||||
|
||||
view.viewICS = ko.observable(null);
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
view.message.subscribe(msg => {
|
||||
view.viewICS(null);
|
||||
if (msg) {
|
||||
// let ics = msg.attachments.find(attachment => 'application/ics' == attachment.mimeType);
|
||||
let ics = msg.attachments.find(attachment => 'text/calendar' == attachment.mimeType);
|
||||
if (ics && ics.download) {
|
||||
// fetch it and parse the VEVENT
|
||||
rl.fetch(ics.linkDownload())
|
||||
.then(response => (response.status < 400) ? response.text() : Promise.reject(new Error({ response })))
|
||||
.then(text => {
|
||||
let VEVENT,
|
||||
VALARM,
|
||||
multiple = ['ATTACH','ATTENDEE','CATEGORIES','COMMENT','CONTACT','EXDATE',
|
||||
'EXRULE','RSTATUS','RELATED','RESOURCES','RDATE','RRULE'],
|
||||
lines = text.split(/\r?\n/),
|
||||
i = lines.length;
|
||||
while (i--) {
|
||||
let line = lines[i];
|
||||
if (VEVENT) {
|
||||
while (line.startsWith(' ') && i--) {
|
||||
line = lines[i] + line.slice(1);
|
||||
}
|
||||
if (line.startsWith('END:VALARM')) {
|
||||
VALARM = {};
|
||||
continue;
|
||||
} else if (line.startsWith('BEGIN:VALARM')) {
|
||||
VEVENT.VALARM || (VEVENT.VALARM = []);
|
||||
VEVENT.VALARM.push(VALARM);
|
||||
VALARM = null;
|
||||
continue;
|
||||
} else if (line.startsWith('BEGIN:VEVENT')) {
|
||||
break;
|
||||
}
|
||||
line = line.match(/^([^:;]+)[:;](.+)$/);
|
||||
if (line) {
|
||||
if (VALARM) {
|
||||
VALARM[line[1]] = line[2];
|
||||
} else if (multiple.includes(line[1]) || 'X-' == line[1].slice(0,2)) {
|
||||
VEVENT[line[1]] || (VEVENT[line[1]] = []);
|
||||
VEVENT[line[1]].push(line[2]);
|
||||
} else {
|
||||
if ('DTSTART' === line[1] || 'DTEND' === line[1]) {
|
||||
let parts = dateRegEx.exec(line[2])?.groups,
|
||||
options = {dateStyle: 'long', timeStyle: 'short'};
|
||||
parts.tz && (options.timeZone = parts.tz);
|
||||
line[2] = new Date(
|
||||
parseInt(parts.year, 10),
|
||||
parseInt(parts.month, 10) - 1,
|
||||
parseInt(parts.day, 10),
|
||||
parseInt(parts.hour, 10),
|
||||
parseInt(parts.minute, 10),
|
||||
parseInt(parts.second, 10)
|
||||
).format(options);
|
||||
}
|
||||
VEVENT[line[1]] = line[2];
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith('END:VEVENT')) {
|
||||
VEVENT = {};
|
||||
}
|
||||
}
|
||||
// METHOD:REPLY || METHOD:REQUEST
|
||||
// console.dir({VEVENT:VEVENT});
|
||||
if (VEVENT) {
|
||||
VEVENT.rawText = text;
|
||||
VEVENT.isCancelled = () => VEVENT.STATUS?.includes('CANCELLED');
|
||||
VEVENT.isConfirmed = () => VEVENT.STATUS?.includes('CONFIRMED');
|
||||
VEVENT.shouldReply = () => VEVENT.METHOD?.includes('REPLY');
|
||||
console.dir({
|
||||
isCancelled: VEVENT.isCancelled(),
|
||||
shouldReply: VEVENT.shouldReply()
|
||||
});
|
||||
view.viewICS(VEVENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(window.rl);
|
Loading…
Reference in a new issue