1
0
mirror of https://github.com/axllent/mailpit.git synced 2024-12-28 23:06:43 +02:00

Chore: Refactor JavaScript, use arrow functions instead of "self" aliasing

This commit is contained in:
Ralph Slooten 2024-06-22 13:27:00 +12:00
parent 5e5b855a3d
commit 33e367d706
24 changed files with 357 additions and 396 deletions

View File

@ -26,15 +26,14 @@ export default {
},
methods: {
loadInfo: function () {
let self = this
self.get(self.resolve('/api/v1/info'), false, function (response) {
loadInfo() {
this.get(this.resolve('/api/v1/info'), false, (response) => {
mailbox.appInfo = response.data
self.modal('AppInfoModal').show()
this.modal('AppInfoModal').show()
})
},
requestNotifications: function () {
requestNotifications() {
// check if the browser supports notifications
if (!("Notification" in window)) {
alert("This browser does not support desktop notifications")
@ -42,7 +41,6 @@ export default {
// we need to ask the user for permission
else if (Notification.permission !== "denied") {
let self = this
Notification.requestPermission().then(function (permission) {
if (permission === "granted") {
mailbox.notificationsEnabled = true
@ -180,7 +178,9 @@ export default {
<td>
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
<small class="text-secondary">
({{ getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize) }})
({{
getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize)
}})
</small>
</td>
</tr>
@ -245,6 +245,6 @@ export default {
<Settings />
</template>
<AjaxLoader :loading="loading" />
</template>

View File

@ -39,10 +39,9 @@ export default {
}
this.iconProcessing = true
let self = this
window.setTimeout(() => {
self.icoUpdate()
this.icoUpdate()
}, this.iconTimeout)
},
},

View File

@ -21,29 +21,25 @@ export default {
},
mounted() {
let relativeTime = require('dayjs/plugin/relativeTime')
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)
this.refreshUI()
},
methods: {
refreshUI: function () {
let self = this
window.setTimeout(
() => {
self.$forceUpdate()
self.refreshUI()
},
30000
)
refreshUI() {
window.setTimeout(() => {
this.$forceUpdate()
this.refreshUI()
}, 30000)
},
getRelativeCreated: function (message) {
let d = new Date(message.Created)
getRelativeCreated(message) {
const d = new Date(message.Created)
return dayjs(d).fromNow()
},
getPrimaryEmailTo: function (message) {
getPrimaryEmailTo(message) {
for (let i in message.To) {
return message.To[i].Address
}
@ -51,11 +47,11 @@ export default {
return '[ Undisclosed recipients ]'
},
isSelected: function (id) {
isSelected(id) {
return mailbox.selected.indexOf(id) != -1
},
toggleSelected: function (e, id) {
toggleSelected(e, id) {
e.preventDefault()
if (this.isSelected(id)) {
@ -67,7 +63,7 @@ export default {
}
},
selectRange: function (e, id) {
selectRange(e, id) {
e.preventDefault()
let selecting = false
@ -102,7 +98,7 @@ export default {
}
},
toTagUrl: function (t) {
toTagUrl(t) {
if (t.match(/ /)) {
t = `"${t}"`
}

View File

@ -30,7 +30,7 @@ export default {
},
methods: {
reloadInbox: function () {
reloadInbox() {
const paginationParams = this.getPaginationParams()
const reload = paginationParams?.start ? false : true
@ -41,24 +41,22 @@ export default {
}
},
loadMessages: function () {
loadMessages() {
this.hideNav() // hide mobile menu
this.$emit('loadMessages')
},
markAllRead: function () {
let self = this
self.put(self.resolve(`/api/v1/messages`), { 'read': true }, function (response) {
markAllRead() {
this.put(this.resolve(`/api/v1/messages`), { 'read': true }, (response) => {
window.scrollInPlace = true
self.loadMessages()
this.loadMessages()
})
},
deleteAllMessages: function () {
let self = this
self.delete(self.resolve(`/api/v1/messages`), false, function (response) {
deleteAllMessages() {
this.delete(this.resolve(`/api/v1/messages`), false, (response) => {
pagination.start = 0
self.loadMessages()
this.loadMessages()
})
}
}

View File

@ -30,22 +30,20 @@ export default {
},
methods: {
loadMessages: function () {
loadMessages() {
this.hideNav() // hide mobile menu
this.$emit('loadMessages')
},
deleteAllMessages: function () {
let s = this.getSearch()
deleteAllMessages() {
const s = this.getSearch()
if (!s) {
return
}
let self = this
let uri = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s)
this.delete(uri, false, function (response) {
self.$router.push('/')
const uri = this.resolve(`/api/v1/search`) + '?query=' + encodeURIComponent(s)
this.delete(uri, false, (response) => {
this.$router.push('/')
})
}
}

View File

@ -19,54 +19,52 @@ export default {
},
methods: {
loadMessages: function () {
loadMessages() {
this.$emit('loadMessages')
},
// mark selected messages as read
markSelectedRead: function () {
let self = this
markSelectedRead() {
if (!mailbox.selected.length) {
return false
}
self.put(self.resolve(`/api/v1/messages`), { 'Read': true, 'IDs': mailbox.selected }, function (response) {
this.put(this.resolve(`/api/v1/messages`), { 'Read': true, 'IDs': mailbox.selected }, (response) => {
window.scrollInPlace = true
self.loadMessages()
this.loadMessages()
})
},
isSelected: function (id) {
isSelected(id) {
return mailbox.selected.indexOf(id) != -1
},
// mark selected messages as unread
markSelectedUnread: function () {
let self = this
markSelectedUnread() {
if (!mailbox.selected.length) {
return false
}
self.put(self.resolve(`/api/v1/messages`), { 'Read': false, 'IDs': mailbox.selected }, function (response) {
this.put(this.resolve(`/api/v1/messages`), { 'Read': false, 'IDs': mailbox.selected }, (response) => {
window.scrollInPlace = true
self.loadMessages()
this.loadMessages()
})
},
// universal handler to delete current or selected messages
deleteMessages: function () {
deleteMessages() {
let ids = []
let self = this
ids = JSON.parse(JSON.stringify(mailbox.selected))
if (!ids.length) {
return false
}
self.delete(self.resolve(`/api/v1/messages`), { 'IDs': ids }, function (response) {
this.delete(this.resolve(`/api/v1/messages`), { 'IDs': ids }, (response) => {
window.scrollInPlace = true
self.loadMessages()
this.loadMessages()
})
},
// test if any selected emails are unread
selectedHasUnread: function () {
selectedHasUnread() {
if (!mailbox.selected.length) {
return false
}
@ -79,7 +77,7 @@ export default {
},
// test of any selected emails are read
selectedHasRead: function () {
selectedHasRead() {
if (!mailbox.selected.length) {
return false
}

View File

@ -17,7 +17,7 @@ export default {
methods: {
// test whether a tag is currently being searched for (in the URL)
inSearch: function (tag) {
inSearch(tag) {
const urlParams = new URLSearchParams(window.location.search)
const query = urlParams.get('q')
if (!query) {
@ -29,7 +29,7 @@ export default {
},
// toggle a tag search in the search URL, add or remove it accordingly
toggleTag: function (e, tag) {
toggleTag(e, tag) {
e.preventDefault()
const urlParams = new URLSearchParams(window.location.search)

View File

@ -43,8 +43,7 @@ export default {
// websocket connect
connect() {
const ws = new WebSocket(this.socketURI)
let self = this
ws.onmessage = function (e) {
ws.onmessage = (e) => {
let response
try {
response = JSON.parse(e.data)
@ -65,7 +64,7 @@ export default {
// update pagination offset
pagination.start++
// prevent "Too many calls to Location or History APIs within a short timeframe"
self.delayedPaginationUpdate()
this.delayedPaginationUpdate()
}
}
@ -79,13 +78,13 @@ export default {
}
// send notifications
if (!self.pauseNotifications) {
self.pauseNotifications = true
if (!this.pauseNotifications) {
this.pauseNotifications = true
let from = response.Data.From != null ? response.Data.From.Address : '[unknown]'
self.browserNotify("New mail from: " + from, response.Data.Subject)
self.setMessageToast(response.Data)
this.browserNotify("New mail from: " + from, response.Data.Subject)
this.setMessageToast(response.Data)
// delay notifications by 2s
window.setTimeout(() => { self.pauseNotifications = false }, 2000)
window.setTimeout(() => { this.pauseNotifications = false }, 2000)
}
} else if (response.Type == "prune") {
// messages have been deleted, reload messages to adjust
@ -98,24 +97,24 @@ export default {
mailbox.unread = response.Data.Unread
// detect version updated, refresh is needed
if (self.version != response.Data.Version) {
if (this.version != response.Data.Version) {
location.reload()
}
}
}
ws.onopen = function () {
ws.onopen = () => {
mailbox.connected = true
self.socketLastConnection = Date.now()
if (self.reconnectRefresh) {
self.reconnectRefresh = false
this.socketLastConnection = Date.now()
if (this.reconnectRefresh) {
this.reconnectRefresh = false
mailbox.refresh = true // trigger refresh
window.setTimeout(() => { mailbox.refresh = false }, 500)
}
}
ws.onclose = function (e) {
if (self.socketLastConnection == 0) {
ws.onclose = (e) => {
if (this.socketLastConnection == 0) {
// connection failed immediately after connecting to Mailpit implies proxy websockets aren't configured
console.log('Unable to connect to websocket, disabling websocket support')
return
@ -123,27 +122,27 @@ export default {
if (mailbox.connected) {
// count disconnections
self.socketBreaks++
this.socketBreaks++
}
// set disconnected state
mailbox.connected = false
if (self.socketBreaks > 3) {
if (this.socketBreaks > 3) {
// give up after > 3 successful socket connections & disconnections within a 15 second window,
// something is not working right on their end, see issue #319
console.log('Unstable websocket connection, disabling websocket support')
return
}
if (Date.now() - self.socketLastConnection > 5000) {
if (Date.now() - this.socketLastConnection > 5000) {
// only refresh mailbox if the last successful connection was broken for > 5 seconds
self.reconnectRefresh = true
this.reconnectRefresh = true
} else {
self.reconnectRefresh = false
this.reconnectRefresh = false
}
setTimeout(function () {
self.connect() // reconnect
setTimeout(() => {
this.connect() // reconnect
}, 1000)
}
@ -214,11 +213,10 @@ export default {
this.toastMessage = m
let self = this
const el = document.getElementById('messageToast')
if (el) {
el.addEventListener('hidden.bs.toast', () => {
self.toastMessage = false
this.toastMessage = false
})
Toast.getOrCreateInstance(el).show()

View File

@ -20,16 +20,16 @@ export default {
},
computed: {
canPrev: function () {
canPrev() {
return pagination.start > 0
},
canNext: function () {
canNext() {
return this.total > (pagination.start + mailbox.messages.length)
},
// returns the number of next X messages
nextMessages: function () {
nextMessages() {
let t = pagination.start + parseInt(pagination.limit, 10)
if (t > this.total) {
t = this.total
@ -40,17 +40,17 @@ export default {
},
methods: {
changeLimit: function () {
changeLimit() {
pagination.start = 0
this.updateQueryParams()
},
viewNext: function () {
viewNext() {
pagination.start = parseInt(pagination.start, 10) + parseInt(pagination.limit, 10)
this.updateQueryParams()
},
viewPrev: function () {
viewPrev() {
let s = pagination.start - pagination.limit
if (s < 0) {
s = 0
@ -59,7 +59,7 @@ export default {
this.updateQueryParams()
},
updateQueryParams: function () {
updateQueryParams() {
const path = this.$route.path
const p = {
...this.$route.query

View File

@ -24,18 +24,18 @@ export default {
},
methods: {
searchFromURL: function () {
searchFromURL() {
const urlParams = new URLSearchParams(window.location.search)
this.search = urlParams.get('q') ? urlParams.get('q') : ''
},
doSearch: function (e) {
doSearch(e) {
pagination.start = 0
if (this.search == '') {
this.$router.push('/')
} else {
const urlParams = new URLSearchParams(window.location.search)
let curr = urlParams.get('q')
const curr = urlParams.get('q')
if (curr && curr == this.search) {
pagination.start = 0
this.$emit('loadMessages')
@ -57,7 +57,7 @@ export default {
e.preventDefault()
},
resetSearch: function () {
resetSearch() {
this.search = ''
this.$router.push('/')
}

View File

@ -16,7 +16,7 @@ export default {
},
watch: {
theme: function (v) {
theme(v) {
if (v == 'auto') {
localStorage.removeItem('theme')
} else {
@ -34,7 +34,7 @@ export default {
},
methods: {
setTheme: function () {
setTheme() {
if (
this.theme === 'auto' &&
window.matchMedia('(prefers-color-scheme: dark)').matches

View File

@ -18,7 +18,7 @@ export default {
},
methods: {
openAttachment: function (part, e) {
openAttachment(part, e) {
let filename = part.FileName
let contentType = part.ContentType
let href = this.resolve('/api/v1/message/' + this.message.ID + '/part/' + part.PartID)

View File

@ -41,9 +41,7 @@ export default {
},
computed: {
summary: function () {
let self = this
summary() {
if (!this.check) {
return false
}
@ -65,8 +63,8 @@ export default {
}
// filter by enabled platforms
let results = o.Results.filter(function (w) {
return self.platforms.indexOf(w.Platform) != -1
let results = o.Results.filter((w) => {
return this.platforms.indexOf(w.Platform) != -1
})
if (results.length == 0) {
@ -98,7 +96,7 @@ export default {
}
let maxPartial = 0, maxUnsupported = 0
result.Warnings.forEach(function (w) {
result.Warnings.forEach((w) => {
let scoreWeight = 1
if (w.Score.Found < result.Total.Nodes) {
// each error is weighted based on the number of occurrences vs: the total message nodes
@ -108,7 +106,7 @@ export default {
// pseudo-classes & at-rules need to be weighted lower as we do not know how many times they
// are actually used in the HTML, and including things like bootstrap styles completely throws
// off the calculation as these dominate.
if (self.isPseudoClassOrAtRule(w.Title)) {
if (this.isPseudoClassOrAtRule(w.Title)) {
scoreWeight = 0.05
w.PseudoClassOrAtRule = true
}
@ -124,15 +122,15 @@ export default {
})
// sort warnings by final score
result.Warnings.sort(function (a, b) {
result.Warnings.sort((a, b) => {
let aWeight = a.Score.Found > result.Total.Nodes ? result.Total.Nodes : a.Score.Found / result.Total.Nodes
let bWeight = b.Score.Found > result.Total.Nodes ? result.Total.Nodes : b.Score.Found / result.Total.Nodes
if (self.isPseudoClassOrAtRule(a.Title)) {
if (this.isPseudoClassOrAtRule(a.Title)) {
aWeight = 0.05
}
if (self.isPseudoClassOrAtRule(b.Title)) {
if (this.isPseudoClassOrAtRule(b.Title)) {
bWeight = 0.05
}
@ -148,7 +146,7 @@ export default {
return result
},
graphSections: function () {
graphSections() {
let s = Math.round(this.summary.Total.Supported)
let p = Math.round(this.summary.Total.Partial)
let u = 100 - s - p
@ -172,7 +170,7 @@ export default {
},
// colors depend on both varying unsupported & partially unsupported percentages
scoreColor: function () {
scoreColor() {
if (this.summary.Total.Unsupported < 5 && this.summary.Total.Partial < 10) {
this.$emit('setBadgeStyle', 'bg-success')
return 'text-success'
@ -197,62 +195,51 @@ export default {
platforms(v) {
localStorage.setItem('html-check-platforms', JSON.stringify(v))
},
// enabled(v) {
// if (!v) {
// localStorage.setItem('htmlCheckDisabled', true)
// this.$emit('setHtmlScore', false)
// } else {
// localStorage.removeItem('htmlCheckDisabled')
// this.doCheck()
// }
// }
},
methods: {
doCheck: function () {
doCheck() {
this.check = false
if (this.message.HTML == "") {
return
}
let self = this
// ignore any error, do not show loader
axios.get(self.resolve('/api/v1/message/' + self.message.ID + '/html-check'), null)
.then(function (result) {
self.check = result.data
self.error = false
axios.get(this.resolve('/api/v1/message/' + this.message.ID + '/html-check'), null)
.then((result) => {
this.check = result.data
this.error = false
// set tooltips
window.setTimeout(function () {
window.setTimeout(() => {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
[...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl))
}, 500)
})
.catch(function (error) {
.catch((error) => {
// handle error
if (error.response && error.response.data) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response.data.Error) {
self.error = error.response.data.Error
this.error = error.response.data.Error
} else {
self.error = error.response.data
this.error = error.response.data
}
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
self.error = 'Error sending data to the server. Please try again.'
this.error = 'Error sending data to the server. Please try again.'
} else {
// Something happened in setting up the request that triggered an Error
self.error = error.message
this.error = error.message
}
})
},
loadConfig: function () {
loadConfig() {
let platforms = localStorage.getItem('html-check-platforms')
if (platforms) {
try {
@ -268,7 +255,7 @@ export default {
},
// return a platform's families (email clients)
families: function (k) {
families(k) {
if (this.check.Platforms[k]) {
return this.check.Platforms[k]
}
@ -277,19 +264,19 @@ export default {
},
// return whether the test string is a pseudo class (:<test>) or at rule (@<test>)
isPseudoClassOrAtRule: function (t) {
isPseudoClassOrAtRule(t) {
return t.match(/^(:|@)/)
},
round: function (v) {
round(v) {
return Math.round(v)
},
round2dm: function (v) {
round2dm(v) {
return Math.round(v * 100) / 100
},
scrollToWarnings: function () {
scrollToWarnings() {
if (!this.$refs.warnings) {
return
}
@ -312,9 +299,9 @@ export default {
<div class="mt-5 mb-3">
<div class="row w-100">
<div class="col-md-8">
<Donut :sections="graphSections" background="var(--bs-body-bg)" :size="180" unit="px" :thickness="20"
has-legend legend-placement="bottom" :total="100" :start-angle="0" :auto-adjust-text-size="true"
@section-click="scrollToWarnings">
<Donut :sections="graphSections" background="var(--bs-body-bg)" :size="180" unit="px"
:thickness="20" has-legend legend-placement="bottom" :total="100" :start-angle="0"
:auto-adjust-text-size="true" @section-click="scrollToWarnings">
<h2 class="m-0" :class="scoreColor" @click="scrollToWarnings">
{{ round2dm(summary.Total.Supported) }}%
</h2>
@ -388,8 +375,9 @@ export default {
<div class="col-sm mt-2 mt-sm-0">
<div class="progress-stacked">
<div class="progress" role="progressbar" aria-label="Supported"
:aria-valuenow="warning.Score.Supported" aria-valuemin="0" aria-valuemax="100"
:style="{ width: warning.Score.Supported + '%' }" title="Supported">
:aria-valuenow="warning.Score.Supported" aria-valuemin="0"
aria-valuemax="100" :style="{ width: warning.Score.Supported + '%' }"
title="Supported">
<div class="progress-bar bg-success">
{{ round(warning.Score.Supported) + '%' }}
</div>
@ -402,8 +390,9 @@ export default {
</div>
</div>
<div class="progress" role="progressbar" aria-label="No"
:aria-valuenow="warning.Score.Unsupported" aria-valuemin="0" aria-valuemax="100"
:style="{ width: warning.Score.Unsupported + '%' }" title="Not supported">
:aria-valuenow="warning.Score.Unsupported" aria-valuemin="0"
aria-valuemax="100" :style="{ width: warning.Score.Unsupported + '%' }"
title="Not supported">
<div class="progress-bar bg-danger">
{{ round(warning.Score.Unsupported) + '%' }}
</div>
@ -420,7 +409,8 @@ export default {
<i class="bi bi-info-circle me-2"></i>
Detected {{ warning.Score.Found }} <code>{{ warning.Title }}</code>
propert<template v-if="warning.Score.Found === 1">y</template><template
v-else>ies</template> in the CSS styles, but unable to test if used or not.
v-else>ies</template> in the CSS
styles, but unable to test if used or not.
</span>
<span v-if="warning.Description != ''" v-html="warning.Description" class="me-2"></span>
</p>
@ -487,9 +477,9 @@ export default {
<div id="col1" class="accordion-collapse collapse"
data-bs-parent="#HTMLCheckAboutAccordion">
<div class="accordion-body">
The support for HTML/CSS messages varies greatly across email clients. HTML check
attempts to calculate the overall support for your email for all selected platforms
to give you some idea of the general compatibility of your HTML email.
The support for HTML/CSS messages varies greatly across email clients. HTML
check attempts to calculate the overall support for your email for all selected
platforms to give you some idea of the general compatibility of your HTML email.
</div>
</div>
</div>
@ -507,29 +497,31 @@ export default {
Internally the original HTML message is run against
<b>{{ check.Total.Tests }}</b> different HTML and CSS tests. All tests
(except for <code>&lt;script&gt;</code>) correspond to a test on
<a href="https://www.caniemail.com/" target="_blank">caniemail.com</a>, and the
final score is calculated using the available compatibility data.
<a href="https://www.caniemail.com/" target="_blank">caniemail.com</a>, and
the final score is calculated using the available compatibility data.
</p>
<p>
CSS support is very difficult to programmatically test, especially if a message
contains CSS style blocks or is linked to remote stylesheets. Remote stylesheets
are, unless blocked via <code>--block-remote-css-and-fonts</code>, downloaded
and injected into the message as style blocks. The email is then
<a href="https://github.com/vanng822/go-premailer" target="_blank">inlined</a>
CSS support is very difficult to programmatically test, especially if a
message contains CSS style blocks or is linked to remote stylesheets. Remote
stylesheets are, unless blocked via
<code>--block-remote-css-and-fonts</code>,
downloaded and injected into the message as style blocks. The email is then
<a href="https://github.com/vanng822/go-premailer"
target="_blank">inlined</a>
to matching HTML elements. This gives Mailpit fairly accurate results.
</p>
<p>
CSS properties such as <code>@font-face</code>, <code>:visited</code>,
<code>:hover</code> etc cannot be inlined however, so these are searched for
within CSS blocks. This method is not accurate as Mailpit does not know how many
nodes it actually applies to, if any, so they are weighted lightly (5%) as not
to affect the score. An example of this would be any email linking to the full
bootstrap CSS which contains dozens of unused attributes.
within CSS blocks. This method is not accurate as Mailpit does not know how
many nodes it actually applies to, if any, so they are weighted lightly (5%)
as not to affect the score. An example of this would be any email linking to
the full bootstrap CSS which contains dozens of unused attributes.
</p>
<p>
All warnings are displayed with their respective support, including any specific
notes, and it is up to you to decide what you do with that information and how
badly it may impact your message.
All warnings are displayed with their respective support, including any
specific notes, and it is up to you to decide what you do with that
information and how badly it may impact your message.
</p>
</div>
</div>
@ -552,13 +544,15 @@ export default {
<p>
For each test, Mailpit calculates both the unsupported & partially-supported
percentages in relation to the number of matches against the total number of
nodes (elements) in the HTML. The maximum unsupported and partially-supported
weighted scores are then used for the final score (ie: worst case scenario).
nodes (elements) in the HTML. The maximum unsupported and
partially-supported weighted scores are then used for the final score (ie:
worst case scenario).
</p>
<p>
To try explain this logic in very simple terms: Assuming a
<code>&lt;script&gt;</code> node (element) has 100% failure (not supported in
any email client), and a <code>&lt;p&gt;</code> node has 100% pass (supported).
<code>&lt;script&gt;</code> node (element) has 100% failure (not supported
in any email client), and a <code>&lt;p&gt;</code> node has 100% pass
(supported).
</p>
<ul>
<li>
@ -575,7 +569,8 @@ export default {
</li>
</ul>
<p>
Mailpit will sort the warnings according to their weighted unsupported scores.
Mailpit will sort the warnings according to their weighted unsupported
scores.
</p>
</div>
</div>
@ -591,9 +586,9 @@ export default {
<div id="col4" class="accordion-collapse collapse"
data-bs-parent="#HTMLCheckAboutAccordion">
<div class="accordion-body">
HTML check does not detect if the original HTML is valid. In order to detect applied
styles to every node, the HTML email is run through a parser which is very good at
turning invalid input into valid output. It is what it is...
HTML check does not detect if the original HTML is valid. In order to detect
applied styles to every node, the HTML email is run through a parser which is
very good at turning invalid input into valid output. It is what it is...
</div>
</div>
</div>

View File

@ -1,4 +1,3 @@
<script>
import commonMixins from '../../mixins/CommonMixins'
@ -16,10 +15,9 @@ export default {
},
mounted() {
let self = this;
let uri = self.resolve('/api/v1/message/' + self.message.ID + '/headers')
self.get(uri, false, function (response) {
self.headers = response.data
let uri = this.resolve('/api/v1/message/' + this.message.ID + '/headers')
this.get(uri, false, (response) => {
this.headers = response.data
});
},

View File

@ -64,7 +64,7 @@ export default {
},
computed: {
groupedStatuses: function () {
groupedStatuses() {
let results = {}
if (!this.check) {
@ -114,7 +114,7 @@ export default {
},
methods: {
doCheck: function () {
doCheck() {
this.check = false
this.loading = true
let uri = this.resolve('/api/v1/message/' + this.message.ID + '/link-check')
@ -122,38 +122,37 @@ export default {
uri += '?follow=true'
}
let self = this
// ignore any error, do not show loader
axios.get(uri, null)
.then(function (result) {
self.check = result.data
self.error = false
.then((result) => {
this.check = result.data
this.error = false
self.$emit('setLinkErrors', result.data.Errors)
this.$emit('setLinkErrors', result.data.Errors)
})
.catch(function (error) {
.catch((error) => {
// handle error
if (error.response && error.response.data) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response.data.Error) {
self.error = error.response.data.Error
this.error = error.response.data.Error
} else {
self.error = error.response.data
this.error = error.response.data
}
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
self.error = 'Error sending data to the server. Please try again.'
this.error = 'Error sending data to the server. Please try again.'
} else {
// Something happened in setting up the request that triggered an Error
self.error = error.message
this.error = error.message
}
})
.then(function (result) {
.then((result) => {
// always run
self.loading = false
this.loading = false
})
},
}
@ -239,7 +238,8 @@ export default {
</div>
<div class="modal fade" id="LinkCheckOptions" tabindex="-1" aria-labelledby="LinkCheckOptionsLabel" aria-hidden="true">
<div class="modal fade" id="LinkCheckOptions" tabindex="-1" aria-labelledby="LinkCheckOptionsLabel"
aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
@ -296,11 +296,12 @@ export default {
What is Link check?
</button>
</h2>
<div id="col1" class="accordion-collapse collapse" data-bs-parent="#LinkCheckAboutAccordion">
<div id="col1" class="accordion-collapse collapse"
data-bs-parent="#LinkCheckAboutAccordion">
<div class="accordion-body">
Link check scans your message HTML and text for all unique links, images and linked
stylesheets. It then does a HTTP <code>HEAD</code> request to each link, 5 at a time, to
test whether the link/image/stylesheet exists.
stylesheets. It then does a HTTP <code>HEAD</code> request to each link, 5 at a
time, to test whether the link/image/stylesheet exists.
</div>
</div>
</div>
@ -311,14 +312,16 @@ export default {
What are "301" and "302" links?
</button>
</h2>
<div id="col2" class="accordion-collapse collapse" data-bs-parent="#LinkCheckAboutAccordion">
<div id="col2" class="accordion-collapse collapse"
data-bs-parent="#LinkCheckAboutAccordion">
<div class="accordion-body">
<p>
These are links that redirect you to another URL, for example newsletters
often use redirect links to track user clicks.
</p>
<p>
By default Link check will not follow these links, however you can turn this on via
By default Link check will not follow these links, however you can turn this on
via
the settings and Link check will "follow" those redirects.
</p>
</div>
@ -331,7 +334,8 @@ export default {
Why are some links returning an error but work in my browser?
</button>
</h2>
<div id="col3" class="accordion-collapse collapse" data-bs-parent="#LinkCheckAboutAccordion">
<div id="col3" class="accordion-collapse collapse"
data-bs-parent="#LinkCheckAboutAccordion">
<div class="accordion-body">
<p>This may be due to various reasons, for instance:</p>
<ul>
@ -353,11 +357,12 @@ export default {
What are the risks of running Link check automatically?
</button>
</h2>
<div id="col4" class="accordion-collapse collapse" data-bs-parent="#LinkCheckAboutAccordion">
<div id="col4" class="accordion-collapse collapse"
data-bs-parent="#LinkCheckAboutAccordion">
<div class="accordion-body">
<p>
Depending on the type of messages you are testing, opening all links on all messages
may have undesired consequences:
Depending on the type of messages you are testing, opening all links on all
messages may have undesired consequences:
</p>
<ul>
<li>If the message contains tracking links this may reveal your identity.</li>
@ -366,13 +371,13 @@ export default {
unsubscribe you.
</li>
<li>
To speed up the checking process, Link check will attempt 5 URLs at a time. This
could lead to temporary heady load on the remote server.
To speed up the checking process, Link check will attempt 5 URLs at a time.
This could lead to temporary heady load on the remote server.
</li>
</ul>
<p>
Unless you know what messages you receive, it is advised to only run the Link check
manually.
Unless you know what messages you receive, it is advised to only run the Link
check manually.
</p>
</div>
</div>

View File

@ -61,16 +61,15 @@ export default {
scaleHTMLPreview(v) {
if (v == 'display') {
let self = this
window.setTimeout(function () {
self.resizeIFrames()
window.setTimeout(() => {
this.resizeIFrames()
}, 500)
}
}
},
computed: {
hasAnyChecksEnabled: function () {
hasAnyChecksEnabled() {
return (mailbox.showHTMLCheck && this.message.HTML)
|| mailbox.showLinkCheck
|| (mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin)
@ -78,56 +77,53 @@ export default {
},
mounted() {
let self = this
self.canSaveTags = false
self.messageTags = self.message.Tags
self.renderUI()
this.canSaveTags = false
this.messageTags = this.message.Tags
this.renderUI()
window.addEventListener("resize", self.resizeIFrames)
window.addEventListener("resize", this.resizeIFrames)
let headersTab = document.getElementById('nav-headers-tab')
headersTab.addEventListener('shown.bs.tab', function (event) {
self.loadHeaders = true
headersTab.addEventListener('shown.bs.tab', (event) => {
this.loadHeaders = true
})
let rawTab = document.getElementById('nav-raw-tab')
rawTab.addEventListener('shown.bs.tab', function (event) {
self.srcURI = self.resolve('/api/v1/message/' + self.message.ID + '/raw')
self.resizeIFrames()
rawTab.addEventListener('shown.bs.tab', (event) => {
this.srcURI = this.resolve('/api/v1/message/' + this.message.ID + '/raw')
this.resizeIFrames()
})
// manually refresh tags
self.get(self.resolve(`/api/v1/tags`), false, function (response) {
self.availableTags = response.data
self.$nextTick(function () {
this.get(this.resolve(`/api/v1/tags`), false, (response) => {
this.availableTags = response.data
this.$nextTick(() => {
Tags.init('select[multiple]')
// delay tag change detection to allow Tags to load
window.setTimeout(function () {
self.canSaveTags = true
window.setTimeout(() => {
this.canSaveTags = true
}, 200)
})
})
},
methods: {
isHTMLTabSelected: function () {
isHTMLTabSelected() {
this.showMobileButtons = this.$refs.navhtml
&& this.$refs.navhtml.classList.contains('active')
},
renderUI: function () {
let self = this
renderUI() {
// activate the first non-disabled tab
document.querySelector('#nav-tab button:not([disabled])').click()
document.activeElement.blur() // blur focus
document.getElementById('message-view').scrollTop = 0
self.isHTMLTabSelected()
this.isHTMLTabSelected()
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach(function (listObj) {
listObj.addEventListener('shown.bs.tab', function (event) {
self.isHTMLTabSelected()
document.querySelectorAll('button[data-bs-toggle="tab"]').forEach((listObj) => {
listObj.addEventListener('shown.bs.tab', (event) => {
this.isHTMLTabSelected()
})
})
@ -135,7 +131,7 @@ export default {
[...tooltipTriggerList].map(tooltipTriggerEl => new Tooltip(tooltipTriggerEl))
// delay 0.2s until vue has rendered the iframe content
window.setTimeout(function () {
window.setTimeout(() => {
let p = document.getElementById('preview-html')
if (p) {
// make links open in new window
@ -148,7 +144,7 @@ export default {
anchorEl.setAttribute('target', '_blank')
}
}
self.resizeIFrames()
this.resizeIFrames()
}
}, 200)
@ -158,12 +154,12 @@ export default {
Prism.highlightAll()
},
resizeIframe: function (el) {
resizeIframe(el) {
let i = el.target
i.style.height = i.contentWindow.document.body.scrollHeight + 50 + 'px'
},
resizeIFrames: function () {
resizeIFrames() {
if (this.scaleHTMLPreview != 'display') {
return
}
@ -175,7 +171,7 @@ export default {
},
// set the iframe body & text colors based on current theme
initRawIframe: function (el) {
initRawIframe(el) {
let bodyStyles = window.getComputedStyle(document.body, null)
let bg = bodyStyles.getPropertyValue('background-color')
let txt = bodyStyles.getPropertyValue('color')
@ -189,27 +185,25 @@ export default {
this.resizeIframe(el)
},
sanitizeHTML: function (h) {
sanitizeHTML(h) {
// remove <base/> tag if set
return h.replace(/<base .*>/mi, '')
},
saveTags: function () {
let self = this
saveTags() {
var data = {
IDs: [this.message.ID],
Tags: this.messageTags
}
self.put(self.resolve('/api/v1/tags'), data, function (response) {
this.put(this.resolve('/api/v1/tags'), data, (response) => {
window.scrollInPlace = true
self.$emit('loadMessages')
this.$emit('loadMessages')
})
},
// Convert plain text to HTML including anchor links
textToHTML: function (s) {
textToHTML(s) {
let html = s
// full links with http(s)

View File

@ -46,26 +46,25 @@ export default {
methods: {
// triggered manually after modal is shown
initTags: function () {
initTags() {
Tags.init("select[multiple]")
},
releaseMessage: function () {
let self = this
releaseMessage() {
// set timeout to allow for user clicking send before the tag filter has applied the tag
window.setTimeout(function () {
if (!self.addresses.length) {
window.setTimeout(() => {
if (!this.addresses.length) {
return false
}
let data = {
To: self.addresses
To: this.addresses
}
self.post(self.resolve('/api/v1/message/' + self.message.ID + '/release'), data, function (response) {
self.modal("ReleaseModal").hide()
if (self.deleteAfterRelease) {
self.$emit('delete')
this.post(this.resolve('/api/v1/message/' + this.message.ID + '/release'), data, (response) => {
this.modal("ReleaseModal").hide()
if (this.deleteAfterRelease) {
this.$emit('delete')
}
})
}, 100)

View File

@ -1,4 +1,3 @@
<script>
import AjaxLoader from '../AjaxLoader.vue'
import CommonMixins from '../../mixins/CommonMixins'
@ -23,9 +22,8 @@ export default {
},
methods: {
initScreenshot: function () {
initScreenshot() {
this.loading = 1
let self = this
// remove base tag, if set
let h = this.message.HTML.replace(/<base .*>/mi, '')
let proxy = this.resolve('/proxy')
@ -38,11 +36,11 @@ export default {
// update any inline `url(...)` absolute links
const urlRegex = /(url\((\'|\")?(https?:\/\/[^\)\'\"]+)(\'|\")?\))/mgi;
h = h.replaceAll(urlRegex, function (match, p1, p2, p3) {
h = h.replaceAll(urlRegex, (match, p1, p2, p3) => {
if (typeof p2 === 'string') {
return `url(${p2}${proxy}?url=` + encodeURIComponent(self.decodeEntities(p3)) + `${p2})`
return `url(${p2}${proxy}?url=` + encodeURIComponent(this.decodeEntities(p3)) + `${p2})`
}
return `url(${proxy}?url=` + encodeURIComponent(self.decodeEntities(p3)) + `)`
return `url(${proxy}?url=` + encodeURIComponent(this.decodeEntities(p3)) + `)`
})
// create temporary document to manipulate
@ -63,7 +61,7 @@ export default {
let src = i.getAttribute('href')
if (src && src.match(/^https?:\/\//i) && src.indexOf(window.location.origin + window.location.pathname) !== 0) {
i.setAttribute('href', `${proxy}?url=` + encodeURIComponent(self.decodeEntities(src)))
i.setAttribute('href', `${proxy}?url=` + encodeURIComponent(this.decodeEntities(src)))
}
}
@ -72,7 +70,7 @@ export default {
for (let i of images) {
let src = i.getAttribute('src')
if (src && src.match(/^https?:\/\//i) && src.indexOf(window.location.origin + window.location.pathname) !== 0) {
i.setAttribute('src', `${proxy}?url=` + encodeURIComponent(self.decodeEntities(src)))
i.setAttribute('src', `${proxy}?url=` + encodeURIComponent(this.decodeEntities(src)))
}
}
@ -83,7 +81,7 @@ export default {
if (src && src.match(/^https?:\/\//i) && src.indexOf(window.location.origin + window.location.pathname) !== 0) {
// replace with proxy link
i.setAttribute('background', `${proxy}?url=` + encodeURIComponent(self.decodeEntities(src)))
i.setAttribute('background', `${proxy}?url=` + encodeURIComponent(this.decodeEntities(src)))
}
}
@ -92,7 +90,7 @@ export default {
},
// HTML decode function
decodeEntities: function (s) {
decodeEntities(s) {
let e = document.createElement('div')
e.innerHTML = s
let str = e.textContent
@ -100,8 +98,7 @@ export default {
return str
},
doScreenshot: function () {
let self = this
doScreenshot() {
let width = document.getElementById('message-view').getBoundingClientRect().width
let prev = document.getElementById('preview-html')
@ -113,7 +110,7 @@ export default {
width = 300
}
let i = document.getElementById('screenshot-html')
const i = document.getElementById('screenshot-html')
// set the iframe width
i.style.width = width + 'px'
@ -127,11 +124,11 @@ export default {
width: width,
}).then(dataUrl => {
const link = document.createElement('a')
link.download = self.message.ID + '.png'
link.download = this.message.ID + '.png'
link.href = dataUrl
link.click()
self.loading = 0
self.html = false
this.loading = 0
this.html = false
})
}
}

View File

@ -38,41 +38,39 @@ export default {
},
methods: {
doCheck: function () {
doCheck() {
this.check = false
let self = this
// ignore any error, do not show loader
axios.get(self.resolve('/api/v1/message/' + self.message.ID + '/sa-check'), null)
.then(function (result) {
self.check = result.data
self.error = false
self.setIcons()
axios.get(this.resolve('/api/v1/message/' + this.message.ID + '/sa-check'), null)
.then((result) => {
this.check = result.data
this.error = false
this.setIcons()
})
.catch(function (error) {
.catch((error) => {
// handle error
if (error.response && error.response.data) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (error.response.data.Error) {
self.error = error.response.data.Error
this.error = error.response.data.Error
} else {
self.error = error.response.data
this.error = error.response.data
}
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
self.error = 'Error sending data to the server. Please try again.'
this.error = 'Error sending data to the server. Please try again.'
} else {
// Something happened in setting up the request that triggered an Error
self.error = error.message
this.error = error.message
}
})
},
badgeStyle: function (ignorePadding = false) {
badgeStyle(ignorePadding = false) {
let badgeStyle = 'bg-success'
if (this.check.Error) {
badgeStyle = 'bg-warning text-primary'
@ -90,7 +88,7 @@ export default {
return badgeStyle
},
setIcons: function () {
setIcons() {
let score = this.check.Score
if (this.check.Error && this.check.Error != '') {
score = '!'
@ -102,7 +100,7 @@ export default {
},
computed: {
graphSections: function () {
graphSections() {
let score = this.check.Score
let p = Math.round(score / 5 * 100)
if (p > 100) {
@ -125,7 +123,7 @@ export default {
]
},
scoreColor: function () {
scoreColor() {
return this.graphSections[0].color
},
}

View File

@ -2,7 +2,7 @@ import axios from 'axios'
import dayjs from 'dayjs'
import ColorHash from 'color-hash'
import { Modal, Offcanvas } from 'bootstrap'
import {limitOptions} from "../stores/pagination";
import { limitOptions } from "../stores/pagination";
// BootstrapElement is used to return a fake Bootstrap element
// if the ID returns nothing to prevent errors.
@ -25,15 +25,15 @@ export default {
},
methods: {
resolve: function (u) {
resolve(u) {
return this.$router.resolve(u).href
},
searchURI: function (s) {
searchURI(s) {
return this.resolve('/search') + '?q=' + encodeURIComponent(s)
},
getFileSize: function (bytes) {
getFileSize(bytes) {
if (bytes == 0) {
return '0B'
}
@ -41,19 +41,19 @@ export default {
return (bytes / Math.pow(1024, i)).toFixed(1) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
},
formatNumber: function (nr) {
formatNumber(nr) {
return new Intl.NumberFormat().format(nr)
},
messageDate: function (d) {
messageDate(d) {
return dayjs(d).format('ddd, D MMM YYYY, h:mm a')
},
secondsToRelative: function (d) {
secondsToRelative(d) {
return dayjs().subtract(d, 'seconds').fromNow()
},
tagEncodeURI: function (tag) {
tagEncodeURI(tag) {
if (tag.match(/ /)) {
tag = `"${tag}"`
}
@ -61,7 +61,7 @@ export default {
return encodeURIComponent(`tag:${tag}`)
},
getSearch: function () {
getSearch() {
if (!window.location.search) {
return false
}
@ -75,7 +75,7 @@ export default {
return q
},
getPaginationParams: function () {
getPaginationParams() {
if (!window.location.search) {
return null
}
@ -90,8 +90,8 @@ export default {
},
// generic modal get/set function
modal: function (id) {
let e = document.getElementById(id)
modal(id) {
const e = document.getElementById(id)
if (e) {
return Modal.getOrCreateInstance(e)
}
@ -100,8 +100,8 @@ export default {
},
// close mobile navigation
hideNav: function () {
let e = document.getElementById('offcanvas')
hideNav() {
const e = document.getElementById('offcanvas')
if (e) {
Offcanvas.getOrCreateInstance(e).hide()
}
@ -115,22 +115,21 @@ export default {
* @params function callback function
* @params function error callback function
*/
get: function (url, values, callback, errorCallback) {
let self = this
self.loading++
get(url, values, callback, errorCallback) {
this.loading++
axios.get(url, { params: values })
.then(callback)
.catch(function (err) {
.catch((err) => {
if (typeof errorCallback == 'function') {
return errorCallback(err)
}
self.handleError(err)
this.handleError(err)
})
.then(function () {
.then(() => {
// always executed
if (self.loading > 0) {
self.loading--
if (this.loading > 0) {
this.loading--
}
})
},
@ -142,16 +141,15 @@ export default {
* @params array object/array values
* @params function callback function
*/
post: function (url, data, callback) {
let self = this
self.loading++
post(url, data, callback) {
this.loading++
axios.post(url, data)
.then(callback)
.catch(self.handleError)
.then(function () {
.catch(this.handleError)
.then(() => {
// always executed
if (self.loading > 0) {
self.loading--
if (this.loading > 0) {
this.loading--
}
})
},
@ -163,16 +161,15 @@ export default {
* @params array object/array values
* @params function callback function
*/
delete: function (url, data, callback) {
let self = this
self.loading++
delete(url, data, callback) {
this.loading++
axios.delete(url, { data: data })
.then(callback)
.catch(self.handleError)
.then(function () {
.catch(this.handleError)
.then(() => {
// always executed
if (self.loading > 0) {
self.loading--
if (this.loading > 0) {
this.loading--
}
})
},
@ -184,22 +181,21 @@ export default {
* @params array object/array values
* @params function callback function
*/
put: function (url, data, callback) {
let self = this
self.loading++
put(url, data, callback) {
this.loading++
axios.put(url, data)
.then(callback)
.catch(self.handleError)
.then(function () {
.catch(this.handleError)
.then(() => {
// always executed
if (self.loading > 0) {
self.loading--
if (this.loading > 0) {
this.loading--
}
})
},
// Ajax error message
handleError: function (error) {
handleError(error) {
// handle error
if (error.response && error.response.data) {
// The request was made and the server responded with a status code
@ -218,7 +214,7 @@ export default {
}
},
allAttachments: function (message) {
allAttachments(message) {
let a = []
for (let i in message.Attachments) {
a.push(message.Attachments[i])
@ -237,7 +233,7 @@ export default {
return a.ContentType.match(/^image\//)
},
attachmentIcon: function (a) {
attachmentIcon(a) {
let ext = a.FileName.split('.').pop().toLowerCase()
if (a.ContentType.match(/^image\//)) {
@ -279,7 +275,7 @@ export default {
// Returns a hex color based on a string.
// Values are stored in an array for faster lookup / processing.
colorHash: function (s) {
colorHash(s) {
if (this.tagColorCache[s] != undefined) {
return this.tagColorCache[s]
}

View File

@ -25,12 +25,12 @@ export default {
},
methods: {
reloadMailbox: function () {
reloadMailbox() {
pagination.start = 0
this.loadMessages()
},
loadMessages: function () {
loadMessages() {
if (!this.apiURI) {
alert('apiURL not set!')
return
@ -43,8 +43,7 @@ export default {
return
}
let self = this
let params = {}
const params = {}
mailbox.selected = []
params['limit'] = pagination.limit
@ -52,7 +51,7 @@ export default {
params['start'] = pagination.start
}
self.get(this.apiURI, params, function (response) {
this.get(this.apiURI, params, (response) => {
mailbox.total = response.data.total // all messages
mailbox.unread = response.data.unread // all unread messages
mailbox.tags = response.data.tags // all tags
@ -63,18 +62,18 @@ export default {
if (response.data.count == 0 && response.data.start > 0) {
pagination.start = 0
return self.loadMessages()
return this.loadMessages()
}
if (mailbox.lastMessage) {
window.setTimeout(() => {
let m = document.getElementById(mailbox.lastMessage)
const m = document.getElementById(mailbox.lastMessage)
if (m) {
m.focus()
// m.scrollIntoView({ behavior: 'smooth', block: 'center' })
m.scrollIntoView({ block: 'center' })
} else {
let mp = document.getElementById('message-page')
const mp = document.getElementById('message-page')
if (mp) {
mp.scrollTop = 0
}
@ -84,7 +83,7 @@ export default {
}, 50)
} else if (!window.scrollInPlace) {
let mp = document.getElementById('message-page')
const mp = document.getElementById('message-page')
if (mp) {
mp.scrollTop = 0
}

View File

@ -43,7 +43,7 @@ export default {
},
methods: {
loadMailbox: function () {
loadMailbox() {
const paginationParams = this.getPaginationParams()
if (paginationParams?.start) {
pagination.start = paginationParams.start

View File

@ -41,16 +41,14 @@ export default {
},
methods: {
loadMessage: function () {
let self = this
loadMessage() {
this.message = false
let uri = self.resolve('/api/v1/message/' + this.$route.params.id)
self.get(uri, false, function (response) {
self.errorMessage = false
const uri = this.resolve('/api/v1/message/' + this.$route.params.id)
this.get(uri, false, (response) => {
this.errorMessage = false
const d = response.data
let d = response.data
if (self.wasUnread(d.ID)) {
if (this.wasUnread(d.ID)) {
mailbox.unread--
}
@ -61,14 +59,14 @@ export default {
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'
'$1' + this.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'
'$1' + this.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
)
}
}
@ -81,43 +79,43 @@ export default {
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'
'$1' + this.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'
'$1' + this.resolve('/api/v1/message/' + d.ID + '/part/' + a.PartID) + '$3'
)
}
}
}
self.message = d
this.message = d
self.detectPrevNext()
this.detectPrevNext()
},
function (error) {
self.errorMessage = true
(error) => {
this.errorMessage = true
if (error.response && error.response.data) {
if (error.response.data.Error) {
self.errorMessage = error.response.data.Error
this.errorMessage = error.response.data.Error
} else {
self.errorMessage = error.response.data
this.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.'
this.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
this.errorMessage = error.message
}
})
},
// try detect whether this message was unread based on messages listing
wasUnread: function (id) {
wasUnread(id) {
for (let m in mailbox.messages) {
if (mailbox.messages[m].ID == id) {
if (!mailbox.messages[m].Read) {
@ -129,7 +127,7 @@ export default {
}
},
detectPrevNext: function () {
detectPrevNext() {
// generate the prev/next links based on current message list
this.prevLink = false
this.nextLink = false
@ -147,40 +145,38 @@ export default {
}
},
downloadMessageBody: function (str, ext) {
let dl = document.createElement('a')
downloadMessageBody(str, ext) {
const dl = document.createElement('a')
dl.href = "data:text/plain," + encodeURIComponent(str)
dl.target = '_blank'
dl.download = this.message.ID + '.' + ext
dl.click()
},
screenshotMessageHTML: function () {
screenshotMessageHTML() {
this.$refs.ScreenshotRef.initScreenshot()
},
// mark current message as read
markUnread: function () {
let self = this
if (!self.message) {
markUnread() {
if (!this.message) {
return false
}
let uri = self.resolve('/api/v1/messages')
self.put(uri, { 'read': false, 'ids': [self.message.ID] }, function (response) {
self.goBack()
const uri = this.resolve('/api/v1/messages')
this.put(uri, { 'read': false, 'ids': [this.message.ID] }, (response) => {
this.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()
deleteMessage() {
const ids = [this.message.ID]
const uri = this.resolve('/api/v1/messages')
this.delete(uri, { 'ids': ids }, () => {
this.goBack()
})
},
goBack: function () {
goBack() {
mailbox.lastMessage = this.$route.params.id
if (mailbox.searching) {
@ -208,16 +204,13 @@ export default {
}
},
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)
initReleaseModal() {
this.modal('ReleaseModal').show()
window.setTimeout(() => {
// delay to allow elements to load / focus
this.$refs.ReleaseRef.initTags()
document.querySelector('#ReleaseModal input[role="combobox"]').focus()
}, 500)
},
}
}

View File

@ -43,8 +43,8 @@ export default {
},
methods: {
doSearch: function () {
let s = this.getSearch()
doSearch() {
const s = this.getSearch()
if (!s) {
mailbox.searching = false