mirror of
https://github.com/axllent/mailpit.git
synced 2025-02-03 13:12:03 +02:00
e902806ea2
See #169
347 lines
11 KiB
Vue
347 lines
11 KiB
Vue
<script>
|
|
import AboutMailpit from '../components/AboutMailpit.vue'
|
|
import AjaxLoader from '../components/AjaxLoader.vue'
|
|
import CommonMixins from '../mixins/CommonMixins'
|
|
import Message from '../components/message/Message.vue'
|
|
import Release from '../components/message/Release.vue'
|
|
import Screenshot from '../components/message/Screenshot.vue'
|
|
import { mailbox } from '../stores/mailbox'
|
|
import { pagination } from '../stores/pagination'
|
|
|
|
export default {
|
|
mixins: [CommonMixins],
|
|
|
|
components: {
|
|
AboutMailpit,
|
|
AjaxLoader,
|
|
Message,
|
|
Screenshot,
|
|
Release,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
mailbox,
|
|
pagination,
|
|
message: false,
|
|
prevLink: false,
|
|
nextLink: false,
|
|
errorMessage: false,
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
$route(to, from) {
|
|
this.loadMessage()
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
this.loadMessage()
|
|
},
|
|
|
|
methods: {
|
|
loadMessage: function () {
|
|
let self = this
|
|
this.message = false
|
|
let uri = self.resolve('/api/v1/message/' + this.$route.params.id)
|
|
self.get(uri, false, function (response) {
|
|
self.errorMessage = false
|
|
|
|
let d = response.data
|
|
|
|
if (self.wasUnread(d.ID)) {
|
|
mailbox.unread--
|
|
}
|
|
|
|
// replace inline images embedded as inline attachments
|
|
if (d.HTML && d.Inline) {
|
|
for (let i in d.Inline) {
|
|
let a = d.Inline[i]
|
|
if (a.ContentID != '') {
|
|
d.HTML = d.HTML.replace(
|
|
new RegExp('(=["\']?)(cid:' + a.ContentID + ')(["|\'|\\s|\\/|>|;])', 'g'),
|
|
'$1' + self.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
|
|
)
|
|
}
|
|
if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) {
|
|
// some old email clients use the filename
|
|
d.HTML = d.HTML.replace(
|
|
new RegExp('(=["\']?)(' + a.FileName + ')(["|\'|\\s|\\/|>|;])', 'g'),
|
|
'$1' + self.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// replace inline images embedded as regular attachments
|
|
if (d.HTML && d.Attachments) {
|
|
for (let i in d.Attachments) {
|
|
let a = d.Attachments[i]
|
|
if (a.ContentID != '') {
|
|
d.HTML = d.HTML.replace(
|
|
new RegExp('(=["\']?)(cid:' + a.ContentID + ')(["|\'|\\s|\\/|>|;])', 'g'),
|
|
'$1' + self.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
|
|
)
|
|
}
|
|
if (a.FileName.match(/^[a-zA-Z0-9\_\-\.]+$/)) {
|
|
// some old email clients use the filename
|
|
d.HTML = d.HTML.replace(
|
|
new RegExp('(=["\']?)(' + a.FileName + ')(["|\'|\\s|\\/|>|;])', 'g'),
|
|
'$1' + self.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
self.message = d
|
|
|
|
self.detectPrevNext()
|
|
},
|
|
function (error) {
|
|
self.errorMessage = true
|
|
if (error.response && error.response.data) {
|
|
if (error.response.data.Error) {
|
|
self.errorMessage = error.response.data.Error
|
|
} else {
|
|
self.errorMessage = error.response.data
|
|
}
|
|
} else if (error.request) {
|
|
// The request was made but no response was received
|
|
self.errorMessage = 'Error sending data to the server. Please refresh the page.'
|
|
} else {
|
|
// Something happened in setting up the request that triggered an Error
|
|
self.errorMessage = error.message
|
|
}
|
|
})
|
|
},
|
|
|
|
// try detect whether this message was unread based on messages listing
|
|
wasUnread: function (id) {
|
|
for (let m in mailbox.messages) {
|
|
if (mailbox.messages[m].ID == id) {
|
|
if (!mailbox.messages[m].Read) {
|
|
mailbox.messages[m].Read = true
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
},
|
|
|
|
detectPrevNext: function () {
|
|
// generate the prev/next links based on current message list
|
|
this.prevLink = false
|
|
this.nextLink = false
|
|
let found = false
|
|
|
|
for (let m in mailbox.messages) {
|
|
if (mailbox.messages[m].ID == this.message.ID) {
|
|
found = true
|
|
} else if (found && !this.nextLink) {
|
|
this.nextLink = mailbox.messages[m].ID
|
|
break
|
|
} else {
|
|
this.prevLink = mailbox.messages[m].ID
|
|
}
|
|
}
|
|
},
|
|
|
|
downloadMessageBody: function (str, ext) {
|
|
let dl = document.createElement('a')
|
|
dl.href = "data:text/plain," + encodeURIComponent(str)
|
|
dl.target = '_blank'
|
|
dl.download = this.message.ID + '.' + ext
|
|
dl.click()
|
|
},
|
|
|
|
screenshotMessageHTML: function () {
|
|
this.$refs.ScreenshotRef.initScreenshot()
|
|
},
|
|
|
|
// mark current message as read
|
|
markUnread: function () {
|
|
let self = this
|
|
if (!self.message) {
|
|
return false
|
|
}
|
|
let uri = self.resolve('/api/v1/messages')
|
|
self.put(uri, { 'read': false, 'ids': [self.message.ID] }, function (response) {
|
|
self.goBack()
|
|
})
|
|
},
|
|
|
|
deleteMessage: function () {
|
|
let self = this
|
|
let ids = [self.message.ID]
|
|
let uri = self.resolve('/api/v1/messages')
|
|
self.delete(uri, { 'ids': ids }, function () {
|
|
self.goBack()
|
|
})
|
|
},
|
|
|
|
goBack: function () {
|
|
mailbox.lastMessage = this.$route.params.id
|
|
|
|
if (mailbox.searching) {
|
|
this.$router.push('/search?q=' + encodeURIComponent(mailbox.searching))
|
|
} else {
|
|
this.$router.push('/')
|
|
}
|
|
},
|
|
|
|
initReleaseModal: function () {
|
|
let self = this
|
|
self.modal('ReleaseModal').show()
|
|
window.setTimeout(function () {
|
|
window.setTimeout(function () {
|
|
// delay to allow elements to load / focus
|
|
self.$refs.ReleaseRef.initTags()
|
|
document.querySelector('#ReleaseModal input[role="combobox"]').focus()
|
|
}, 500)
|
|
}, 300)
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="navbar navbar-expand-lg navbar-dark row flex-shrink-0 bg-primary text-white">
|
|
<div class="d-none d-md-block col-xl-2 col-md-3 col-auto pe-0">
|
|
<RouterLink to="/" class="navbar-brand text-white me-0" @click="pagination.start = 0">
|
|
<img :src="resolve('/mailpit.svg')" alt="Mailpit">
|
|
<span class="ms-2 d-none d-sm-inline">Mailpit</span>
|
|
</RouterLink>
|
|
</div>
|
|
<div class="col col-md-4k col-lg-5 col-xl-6" v-if="!errorMessage">
|
|
<button @click="goBack()" class="btn btn-outline-light me-3 me-sm-4 d-md-none" title="Return to messages">
|
|
<i class="bi bi-arrow-return-left"></i>
|
|
</button>
|
|
<button class="btn btn-outline-light me-1 me-sm-2" title="Mark unread" v-on:click="markUnread">
|
|
<i class="bi bi-eye-slash"></i> <span class="d-none d-md-inline">Mark unread</span>
|
|
</button>
|
|
<button class="btn btn-outline-light me-1 me-sm-2" title="Release message"
|
|
v-if="mailbox.uiConfig.MessageRelay && mailbox.uiConfig.MessageRelay.Enabled" v-on:click="initReleaseModal">
|
|
<i class="bi bi-send"></i> <span class="d-none d-md-inline">Release</span>
|
|
</button>
|
|
<button class="btn btn-outline-light me-1 me-sm-2" title="Delete message" v-on:click="deleteMessage">
|
|
<i class="bi bi-trash-fill"></i> <span class="d-none d-md-inline">Delete</span>
|
|
</button>
|
|
</div>
|
|
<div class="col-auto col-lg-4 col-xl-4 text-end" v-if="!errorMessage">
|
|
<div class="dropdown d-inline-block" id="DownloadBtn">
|
|
<button type="button" class="btn btn-outline-light dropdown-toggle" data-bs-toggle="dropdown"
|
|
aria-expanded="false">
|
|
<i class="bi bi-file-arrow-down-fill"></i>
|
|
<span class="d-none d-md-inline ms-1">Download</span>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end">
|
|
<li>
|
|
<a :href="resolve('/api/v1/message/' + message.ID + '/raw?dl=1')" class="dropdown-item"
|
|
title="Message source including headers, body and attachments">
|
|
Raw message
|
|
</a>
|
|
</li>
|
|
<li v-if="message.HTML">
|
|
<button v-on:click="downloadMessageBody(message.HTML, 'html')" class="dropdown-item">
|
|
HTML body
|
|
</button>
|
|
</li>
|
|
<li v-if="message.HTML">
|
|
<button class="dropdown-item" @click="screenshotMessageHTML()">
|
|
HTML screenshot
|
|
</button>
|
|
</li>
|
|
<li v-if="message.Text">
|
|
<button v-on:click="downloadMessageBody(message.Text, 'txt')" class="dropdown-item">
|
|
Text body
|
|
</button>
|
|
</li>
|
|
<template v-if="allAttachments(message).length">
|
|
<li>
|
|
<hr class="dropdown-divider">
|
|
</li>
|
|
<li>
|
|
<h6 class="dropdown-header">
|
|
Attachment<template v-if="allAttachments(message).length > 1">s</template>
|
|
</h6>
|
|
</li>
|
|
<li v-for="part in allAttachments(message)">
|
|
<RouterLink :to="'/api/v1/message/' + message.ID + '/part/' + part.PartID"
|
|
class="row m-0 dropdown-item d-flex" target="_blank"
|
|
:title="part.FileName != '' ? part.FileName : '[ unknown ]'" style="min-width: 350px">
|
|
<div class="col-auto p-0 pe-1">
|
|
<i class="bi" :class="attachmentIcon(part)"></i>
|
|
</div>
|
|
<div class="col text-truncate p-0 pe-1">
|
|
{{ part.FileName != '' ? part.FileName : '[ unknown ]' }}
|
|
</div>
|
|
<div class="col-auto text-muted small p-0">
|
|
{{ getFileSize(part.Size) }}
|
|
</div>
|
|
</RouterLink>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</div>
|
|
|
|
<RouterLink :to="'/view/' + prevLink" class="btn btn-outline-light ms-1 ms-sm-2 me-1"
|
|
:class="prevLink ? '' : 'disabled'" title="View previous message">
|
|
<i class="bi bi-caret-left-fill"></i>
|
|
</RouterLink>
|
|
<RouterLink :to="'/view/' + nextLink" class="btn btn-outline-light" :class="nextLink ? '' : 'disabled'">
|
|
<i class="bi bi-caret-right-fill" title="View next message"></i>
|
|
</RouterLink>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row flex-fill" style="min-height:0">
|
|
<div class="d-none d-md-block col-xl-2 col-md-3 mh-100 position-relative"
|
|
style="overflow-y: auto; overflow-x: hidden;">
|
|
|
|
<div class="list-group my-2">
|
|
<button @click="goBack()" class="list-group-item list-group-item-action">
|
|
<i class="bi bi-arrow-return-left me-1"></i>
|
|
<span class="ms-1">Return</span>
|
|
<span class="badge rounded-pill ms-1 float-end text-bg-secondary" title="Unread messages"
|
|
v-if="mailbox.unread && !errorMessage">
|
|
{{ formatNumber(mailbox.unread) }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="card mt-4" v-if="!errorMessage">
|
|
<div class="card-body text-body-secondary small">
|
|
<p class="card-text">
|
|
<b>Message date:</b><br>
|
|
<small>{{ messageDate(message.Date) }}</small>
|
|
</p>
|
|
<p class="card-text">
|
|
<b>Size:</b> {{ getFileSize(message.Size) }}
|
|
</p>
|
|
<p class="card-text" v-if="allAttachments(message).length">
|
|
<b>Attachments:</b> {{ allAttachments(message).length }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<AboutMailpit />
|
|
</div>
|
|
|
|
<div class="col-xl-10 col-md-9 mh-100 ps-0 ps-md-2 pe-0">
|
|
<div class="mh-100" style="overflow-y: auto;" id="message-page">
|
|
<template v-if="errorMessage">
|
|
<h3 class="text-center my-3">
|
|
{{ errorMessage }}
|
|
</h3>
|
|
</template>
|
|
<Message v-else-if="message" :key="message.ID" :message="message" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<AboutMailpit modals />
|
|
<AjaxLoader :loading="loading" />
|
|
<Release v-if="message" ref="ReleaseRef" :message="message" @delete="deleteMessage" />
|
|
<Screenshot v-if="message" ref="ScreenshotRef" :message="message" />
|
|
</template>
|