2023-09-22 15:06:03 +12:00
|
|
|
<script>
|
2025-06-20 23:26:06 +12:00
|
|
|
import commonMixins from "../../mixins/CommonMixins";
|
|
|
|
import ICAL from "ical.js";
|
|
|
|
import dayjs from "dayjs";
|
2023-09-22 15:06:03 +12:00
|
|
|
|
|
|
|
export default {
|
2025-06-20 23:26:06 +12:00
|
|
|
mixins: [commonMixins],
|
|
|
|
|
2023-09-22 15:06:03 +12:00
|
|
|
props: {
|
2025-06-20 23:26:06 +12:00
|
|
|
message: {
|
|
|
|
type: Object,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
attachments: {
|
|
|
|
type: Object,
|
|
|
|
required: true,
|
|
|
|
},
|
2023-09-22 15:06:03 +12:00
|
|
|
},
|
|
|
|
|
2024-05-18 23:42:06 +12:00
|
|
|
data() {
|
|
|
|
return {
|
2025-06-20 23:26:06 +12:00
|
|
|
ical: false,
|
|
|
|
};
|
2024-05-18 23:42:06 +12:00
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
2024-06-22 13:27:00 +12:00
|
|
|
openAttachment(part, e) {
|
2025-06-20 23:26:06 +12:00
|
|
|
const filename = part.FileName;
|
|
|
|
const contentType = part.ContentType;
|
|
|
|
const href = this.resolve("/api/v1/message/" + this.message.ID + "/part/" + part.PartID);
|
|
|
|
if (filename.match(/\.ics$/i) || contentType === "text/calendar") {
|
|
|
|
e.preventDefault();
|
2024-05-18 23:42:06 +12:00
|
|
|
|
|
|
|
this.get(href, null, (response) => {
|
2025-06-20 23:26:06 +12:00
|
|
|
const comp = new ICAL.Component(ICAL.parse(response.data));
|
|
|
|
const vevent = comp.getFirstSubcomponent("vevent");
|
2024-05-18 23:42:06 +12:00
|
|
|
if (!vevent) {
|
2025-06-20 23:26:06 +12:00
|
|
|
alert("Error parsing ICS file");
|
|
|
|
return;
|
2024-05-18 23:42:06 +12:00
|
|
|
}
|
2025-06-20 23:26:06 +12:00
|
|
|
const event = new ICAL.Event(vevent);
|
2024-05-18 23:42:06 +12:00
|
|
|
|
2025-06-20 23:26:06 +12:00
|
|
|
const summary = {};
|
|
|
|
summary.link = href;
|
|
|
|
summary.status = vevent.getFirstPropertyValue("status");
|
|
|
|
summary.url = vevent.getFirstPropertyValue("url");
|
|
|
|
summary.summary = event.summary;
|
|
|
|
summary.description = event.description;
|
|
|
|
summary.location = event.location;
|
|
|
|
summary.start = dayjs(event.startDate).format("ddd, D MMM YYYY, h:mm a");
|
|
|
|
summary.end = dayjs(event.endDate).format("ddd, D MMM YYYY, h:mm a");
|
|
|
|
summary.isRecurring = event.isRecurring();
|
|
|
|
summary.organizer = event.organizer ? event.organizer.replace(/^mailto:/, "") : false;
|
|
|
|
summary.attendees = [];
|
2024-05-18 23:42:06 +12:00
|
|
|
event.attendees.forEach((a) => {
|
|
|
|
if (a.jCal[1].cn) {
|
2025-06-20 23:26:06 +12:00
|
|
|
summary.attendees.push(a.jCal[1].cn);
|
2024-05-18 23:42:06 +12:00
|
|
|
}
|
2025-06-20 23:26:06 +12:00
|
|
|
});
|
2024-05-18 23:42:06 +12:00
|
|
|
|
|
|
|
comp.getAllSubcomponents("vtimezone").forEach((vtimezone) => {
|
2025-06-20 23:26:06 +12:00
|
|
|
summary.timezone = vtimezone.getFirstPropertyValue("tzid");
|
|
|
|
});
|
2024-05-18 23:42:06 +12:00
|
|
|
|
2025-06-20 23:26:06 +12:00
|
|
|
this.ical = summary;
|
2024-05-18 23:42:06 +12:00
|
|
|
|
|
|
|
// display modal
|
2025-06-20 23:26:06 +12:00
|
|
|
this.modal("ICSView").show();
|
|
|
|
});
|
2024-05-18 23:42:06 +12:00
|
|
|
}
|
2025-06-20 23:26:06 +12:00
|
|
|
},
|
2024-05-18 23:42:06 +12:00
|
|
|
},
|
2025-06-20 23:26:06 +12:00
|
|
|
};
|
2023-09-22 15:06:03 +12:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<div class="mt-4 border-top pt-4">
|
2025-06-20 23:26:06 +12:00
|
|
|
<a
|
|
|
|
v-for="part in attachments"
|
|
|
|
:key="part.PartID"
|
|
|
|
:href="resolve('/api/v1/message/' + message.ID + '/part/' + part.PartID)"
|
|
|
|
class="card attachment float-start me-3 mb-3"
|
|
|
|
target="_blank"
|
|
|
|
style="width: 180px"
|
|
|
|
@click="openAttachment(part, $event)"
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
v-if="isImage(part)"
|
|
|
|
:src="resolve('/api/v1/message/' + message.ID + '/part/' + part.PartID + '/thumb')"
|
|
|
|
class="card-img-top"
|
|
|
|
alt=""
|
|
|
|
/>
|
|
|
|
<img
|
|
|
|
v-else
|
2023-09-22 15:06:03 +12:00
|
|
|
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAB4AQMAAABhKUq+AAAAA1BMVEX///+nxBvIAAAAGUlEQVQYGe3BgQAAAADDoPtTT+EA1QAAgFsLQAAB12s2WgAAAABJRU5ErkJggg=="
|
2025-06-20 23:26:06 +12:00
|
|
|
class="card-img-top"
|
|
|
|
alt=""
|
|
|
|
/>
|
|
|
|
<div v-if="!isImage(part)" class="icon">
|
2023-09-22 15:06:03 +12:00
|
|
|
<i class="bi" :class="attachmentIcon(part)"></i>
|
|
|
|
</div>
|
|
|
|
<div class="card-body border-0">
|
|
|
|
<p class="mb-1">
|
|
|
|
<i class="bi me-1" :class="attachmentIcon(part)"></i>
|
|
|
|
<small>{{ getFileSize(part.Size) }}</small>
|
|
|
|
</p>
|
|
|
|
<p class="card-text mb-0 small">
|
2025-06-20 23:26:06 +12:00
|
|
|
{{ part.FileName != "" ? part.FileName : "[ unknown ]" + part.ContentType }}
|
2023-09-22 15:06:03 +12:00
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div class="card-footer small border-0 text-center text-truncate">
|
2025-06-20 23:26:06 +12:00
|
|
|
{{ part.FileName != "" ? part.FileName : "[ unknown ]" + part.ContentType }}
|
2023-09-22 15:06:03 +12:00
|
|
|
</div>
|
|
|
|
</a>
|
|
|
|
</div>
|
2024-05-18 23:42:06 +12:00
|
|
|
|
2025-06-20 23:26:06 +12:00
|
|
|
<div id="ICSView" class="modal fade" tabindex="-1" aria-hidden="true">
|
2024-05-18 23:42:06 +12:00
|
|
|
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
|
|
|
|
<div class="modal-content">
|
|
|
|
<div class="modal-header">
|
|
|
|
<h5 class="modal-title fs-5">
|
|
|
|
<i class="bi bi-calendar-event me-2"></i>
|
|
|
|
iCalendar summary
|
|
|
|
</h5>
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
|
</div>
|
2025-06-20 23:26:06 +12:00
|
|
|
<div v-if="ical" class="modal-body">
|
2024-05-18 23:42:06 +12:00
|
|
|
<table class="table">
|
|
|
|
<tbody>
|
|
|
|
<tr v-if="ical.summary">
|
|
|
|
<th>Summary</th>
|
|
|
|
<td>{{ ical.summary }}</td>
|
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.description">
|
|
|
|
<th>Description</th>
|
|
|
|
<td>{{ ical.description }}</td>
|
|
|
|
</tr>
|
|
|
|
<tr>
|
|
|
|
<th>When</th>
|
|
|
|
<td>
|
|
|
|
{{ ical.start }} — {{ ical.end }}
|
|
|
|
<span v-if="ical.isRecurring">(recurring)</span>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.status">
|
|
|
|
<th>Status</th>
|
2025-06-20 23:26:06 +12:00
|
|
|
<td>{{ ical.status }}</td>
|
2024-05-18 23:42:06 +12:00
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.location">
|
|
|
|
<th>Location</th>
|
|
|
|
<td>{{ ical.location }}</td>
|
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.url">
|
|
|
|
<th>URL</th>
|
2025-06-20 23:26:06 +12:00
|
|
|
<td>
|
|
|
|
<a :href="ical.url" target="_blank">{{ ical.url }}</a>
|
|
|
|
</td>
|
2024-05-18 23:42:06 +12:00
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.organizer">
|
|
|
|
<th>Organizer</th>
|
|
|
|
<td>{{ ical.organizer }}</td>
|
|
|
|
</tr>
|
|
|
|
<tr v-if="ical.attendees.length">
|
|
|
|
<th>Attendees</th>
|
|
|
|
<td>
|
2025-06-20 23:26:06 +12:00
|
|
|
<span v-for="(a, i) in ical.attendees" :key="'attendee_' + i">
|
2024-05-18 23:42:06 +12:00
|
|
|
<template v-if="i > 0">,</template>
|
|
|
|
{{ a }}
|
|
|
|
</span>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
<div class="modal-footer">
|
|
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
2025-06-20 23:26:06 +12:00
|
|
|
<a class="btn btn-primary" target="_blank" :href="ical.link"> Download attachment </a>
|
2024-05-18 23:42:06 +12:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-09-22 15:06:03 +12:00
|
|
|
</template>
|