mirror of
https://github.com/axllent/mailpit.git
synced 2025-03-17 21:18:19 +02:00
Feature: Add UI settings screen
This commit is contained in:
parent
a05e4fd48f
commit
faded05e47
6
package-lock.json
generated
6
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"rapidoc": "^9.3.4",
|
"rapidoc": "^9.3.4",
|
||||||
|
"timezones-list": "^3.0.3",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-css-donut-chart": "^2.0.0",
|
"vue-css-donut-chart": "^2.0.0",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
@ -3559,6 +3560,11 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/timezones-list": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/timezones-list/-/timezones-list-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-C+Vdvvj2c1xB6pu81pOX8geo6mrk/QsudFVlTVQET7QQwu8WAIyhDNeCrK5grU7EMzmbKLWqz7uU6dN8fvQvPQ=="
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.29.0",
|
||||||
"rapidoc": "^9.3.4",
|
"rapidoc": "^9.3.4",
|
||||||
|
"timezones-list": "^3.0.3",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.13",
|
||||||
"vue-css-donut-chart": "^2.0.0",
|
"vue-css-donut-chart": "^2.0.0",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
|
@ -15,8 +15,6 @@ export default {
|
|||||||
|
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
document.title = document.title + ' - ' + location.hostname
|
document.title = document.title + ' - ' + location.hostname
|
||||||
mailbox.showTagColors = !localStorage.getItem('hideTagColors') == '1'
|
|
||||||
mailbox.timeZone = localStorage.getItem('timezone') ? localStorage.getItem('timezone') : Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
||||||
|
|
||||||
// load global config
|
// load global config
|
||||||
this.get(this.resolve('/api/v1/webui'), false, function (response) {
|
this.get(this.resolve('/api/v1/webui'), false, function (response) {
|
||||||
|
@ -320,16 +320,18 @@ body.blur {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control.dropdown {
|
#message-view {
|
||||||
padding: 0;
|
.form-control.dropdown {
|
||||||
border: 0;
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
cursor: text; // html5-tags
|
cursor: text; // html5-tags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import AjaxLoader from './AjaxLoader.vue'
|
import AjaxLoader from './AjaxLoader.vue'
|
||||||
|
import Settings from '../components/Settings.vue'
|
||||||
import CommonMixins from '../mixins/CommonMixins'
|
import CommonMixins from '../mixins/CommonMixins'
|
||||||
import { mailbox } from '../stores/mailbox'
|
import { mailbox } from '../stores/mailbox'
|
||||||
|
|
||||||
@ -7,7 +8,8 @@ export default {
|
|||||||
mixins: [CommonMixins],
|
mixins: [CommonMixins],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
AjaxLoader
|
AjaxLoader,
|
||||||
|
Settings,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@ -20,20 +22,9 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mailbox,
|
mailbox,
|
||||||
theme: 'auto',
|
|
||||||
icon: 'circle-half',
|
|
||||||
icons: {
|
|
||||||
'auto': 'circle-half',
|
|
||||||
'light': 'sun-fill',
|
|
||||||
'dark': 'moon-stars-fill'
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.setTheme(this.getPreferredTheme())
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
loadInfo: function () {
|
loadInfo: function () {
|
||||||
let self = this
|
let self = this
|
||||||
@ -43,42 +34,6 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getStoredTheme: function () {
|
|
||||||
let theme = localStorage.getItem('theme')
|
|
||||||
if (!theme) {
|
|
||||||
theme = 'auto'
|
|
||||||
}
|
|
||||||
|
|
||||||
return theme
|
|
||||||
},
|
|
||||||
|
|
||||||
setStoredTheme: function (theme) {
|
|
||||||
localStorage.setItem('theme', theme)
|
|
||||||
this.setTheme(theme)
|
|
||||||
},
|
|
||||||
|
|
||||||
getPreferredTheme: function () {
|
|
||||||
const storedTheme = this.getStoredTheme()
|
|
||||||
if (storedTheme) {
|
|
||||||
return storedTheme
|
|
||||||
}
|
|
||||||
|
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
||||||
},
|
|
||||||
|
|
||||||
setTheme: function (theme) {
|
|
||||||
this.icon = this.icons[theme]
|
|
||||||
this.theme = theme
|
|
||||||
if (
|
|
||||||
theme === 'auto' &&
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
) {
|
|
||||||
document.documentElement.setAttribute('data-bs-theme', 'dark')
|
|
||||||
} else {
|
|
||||||
document.documentElement.setAttribute('data-bs-theme', theme)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
requestNotifications: function () {
|
requestNotifications: function () {
|
||||||
// check if the browser supports notifications
|
// check if the browser supports notifications
|
||||||
if (!("Notification" in window)) {
|
if (!("Notification" in window)) {
|
||||||
@ -101,42 +56,17 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template v-if="!modals">
|
<template v-if="!modals">
|
||||||
<div class="position-fixed bg-body bottom-0 ms-n1 py-2 text-muted small col-xl-2 col-md-3 pe-3 z-3 about-mailpit">
|
<div
|
||||||
|
class="position-fixed bg-body bottom-0 ms-n1 py-2 text-muted small col-xl-2 col-md-3 pe-3 z-3 about-mailpit">
|
||||||
<button class="text-muted btn btn-sm" v-on:click="loadInfo">
|
<button class="text-muted btn btn-sm" v-on:click="loadInfo">
|
||||||
<i class="bi bi-info-circle-fill me-1"></i>
|
<i class="bi bi-info-circle-fill me-1"></i>
|
||||||
About
|
About
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="dropdown bd-mode-toggle float-end me-2 d-inline-block">
|
<button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal"
|
||||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" aria-expanded="false"
|
data-bs-target="#SettingsModal" title="Mailpit UI settings">
|
||||||
title="Toggle theme" data-bs-toggle="dropdown" aria-label="Toggle theme">
|
<i class="bi bi-gear-fill"></i>
|
||||||
<i :class="'bi bi-' + icon + ' my-1'"></i>
|
</button>
|
||||||
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
|
|
||||||
<li>
|
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
|
||||||
:class="theme == 'light' ? 'active' : ''" @click="setStoredTheme('light')">
|
|
||||||
<i class="bi bi-sun-fill me-2 opacity-50"></i>
|
|
||||||
Light
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
|
||||||
:class="theme == 'dark' ? 'active' : ''" @click="setStoredTheme('dark')">
|
|
||||||
<i class="bi bi-moon-stars-fill me-2 opacity-50"></i>
|
|
||||||
Dark
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button type="button" class="dropdown-item d-flex align-items-center"
|
|
||||||
:class="theme == 'auto' ? 'active' : ''" @click="setStoredTheme('auto')">
|
|
||||||
<i class="bi bi-circle-half me-2 opacity-50"></i>
|
|
||||||
Auto
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal"
|
<button class="btn btn-sm btn-outline-secondary float-end me-2" data-bs-toggle="modal"
|
||||||
data-bs-target="#EnableNotificationsModal" title="Enable browser notifications"
|
data-bs-target="#EnableNotificationsModal" title="Enable browser notifications"
|
||||||
@ -167,7 +97,8 @@ export default {
|
|||||||
There might be a newer version available. The check failed.
|
There might be a newer version available. The check failed.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-3" v-else-if="mailbox.appInfo.Version != mailbox.appInfo.LatestVersion">
|
<div class="row g-3"
|
||||||
|
v-else-if="mailbox.appInfo.Version != mailbox.appInfo.LatestVersion">
|
||||||
<a class="btn btn-warning d-block mb-3"
|
<a class="btn btn-warning d-block mb-3"
|
||||||
:href="'https://github.com/axllent/mailpit/releases/tag/' + mailbox.appInfo.LatestVersion">
|
:href="'https://github.com/axllent/mailpit/releases/tag/' + mailbox.appInfo.LatestVersion">
|
||||||
A new version of Mailpit ({{ mailbox.appInfo.LatestVersion }}) is available.
|
A new version of Mailpit ({{ mailbox.appInfo.LatestVersion }}) is available.
|
||||||
@ -197,7 +128,8 @@ export default {
|
|||||||
<div class="card border-secondary text-center">
|
<div class="card border-secondary text-center">
|
||||||
<div class="card-header">Database size</div>
|
<div class="card-header">Database size</div>
|
||||||
<div class="card-body text-secondary">
|
<div class="card-body text-secondary">
|
||||||
<h5 class="card-title">{{ getFileSize(mailbox.appInfo.DatabaseSize) }} </h5>
|
<h5 class="card-title">{{ getFileSize(mailbox.appInfo.DatabaseSize) }}
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -205,8 +137,9 @@ export default {
|
|||||||
<div class="card border-secondary text-center">
|
<div class="card border-secondary text-center">
|
||||||
<div class="card-header">RAM usage</div>
|
<div class="card-header">RAM usage</div>
|
||||||
<div class="card-body text-secondary">
|
<div class="card-body text-secondary">
|
||||||
<h5 class="card-title">{{ getFileSize(mailbox.appInfo.RuntimeStats.Memory)
|
<h5 class="card-title">
|
||||||
}} </h5>
|
{{ getFileSize(mailbox.appInfo.RuntimeStats.Memory) }}
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -216,7 +149,8 @@ export default {
|
|||||||
<div class="card border-secondary">
|
<div class="card border-secondary">
|
||||||
<div class="card-header h4">
|
<div class="card-header h4">
|
||||||
Runtime statistics
|
Runtime statistics
|
||||||
<button class="btn btn-sm btn-outline-secondary float-end" v-on:click="loadInfo">
|
<button class="btn btn-sm btn-outline-secondary float-end"
|
||||||
|
v-on:click="loadInfo">
|
||||||
Refresh
|
Refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -246,9 +180,7 @@ export default {
|
|||||||
<td>
|
<td>
|
||||||
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
|
{{ formatNumber(mailbox.appInfo.RuntimeStats.SMTPAccepted) }}
|
||||||
<small class="text-secondary">
|
<small class="text-secondary">
|
||||||
({{
|
({{ getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize) }})
|
||||||
getFileSize(mailbox.appInfo.RuntimeStats.SMTPAcceptedSize)
|
|
||||||
}})
|
|
||||||
</small>
|
</small>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -285,8 +217,8 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1" aria-labelledby="EnableNotificationsModalLabel"
|
<div class="modal fade" id="EnableNotificationsModal" tabindex="-1"
|
||||||
aria-hidden="true">
|
aria-labelledby="EnableNotificationsModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -298,7 +230,8 @@ export default {
|
|||||||
<p>
|
<p>
|
||||||
Note that your browser will ask you for confirmation when you click
|
Note that your browser will ask you for confirmation when you click
|
||||||
<code>enable notifications</code>,
|
<code>enable notifications</code>,
|
||||||
and that you must have Mailpit open in a browser tab to be able to receive the notifications.
|
and that you must have Mailpit open in a browser tab to be able to receive the
|
||||||
|
notifications.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -309,6 +242,8 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Settings />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<AjaxLoader :loading="loading" />
|
<AjaxLoader :loading="loading" />
|
||||||
|
119
server/ui-src/components/Settings.vue
Normal file
119
server/ui-src/components/Settings.vue
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<script>
|
||||||
|
import CommonMixins from '../mixins/CommonMixins'
|
||||||
|
import Tags from 'bootstrap5-tags'
|
||||||
|
import timezones from 'timezones-list'
|
||||||
|
import { mailbox } from '../stores/mailbox'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [CommonMixins],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mailbox,
|
||||||
|
theme: localStorage.getItem('theme') ? localStorage.getItem('theme') : 'auto',
|
||||||
|
timezones,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
theme: function(v) {
|
||||||
|
if (v == 'auto') {
|
||||||
|
localStorage.removeItem('theme')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('theme', v)
|
||||||
|
}
|
||||||
|
this.setTheme()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.setTheme()
|
||||||
|
this.$nextTick(function () {
|
||||||
|
Tags.init('select.tz')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setTheme: function () {
|
||||||
|
if (
|
||||||
|
this.theme === 'auto' &&
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
) {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', 'dark')
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', this.theme)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="modal fade" id="SettingsModal" tabindex="-1" aria-labelledby="SettingsModalLabel" aria-hidden="true"
|
||||||
|
data-bs-keyboard="false">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="SettingsModalLabel">Mailpit UI settings</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="theme" class="form-label">Mailpit theme</label>
|
||||||
|
<select class="form-select" v-model="theme" id="theme">
|
||||||
|
<option value="auto">Auto (detect from browser)</option>
|
||||||
|
<option value="light">Light theme</option>
|
||||||
|
<option value="dark">Dark theme</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="timezone" class="form-label">Timezone (for date searches)</label>
|
||||||
|
<select class="form-select tz" v-model="mailbox.timeZone" id="timezone">
|
||||||
|
<option disabled hidden value="">Select a timezone...</option>
|
||||||
|
<option v-for="t in timezones" :value="t.tzCode">{{ t.label }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="tagColors"
|
||||||
|
v-model="mailbox.showTagColors">
|
||||||
|
<label class="form-check-label" for="tagColors">
|
||||||
|
Use auto-generated tag colors
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="htmlCheck"
|
||||||
|
v-model="mailbox.showHTMLCheck">
|
||||||
|
<label class="form-check-label" for="htmlCheck">
|
||||||
|
Show HTML check message tab
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="linkCheck"
|
||||||
|
v-model="mailbox.showLinkCheck">
|
||||||
|
<label class="form-check-label" for="linkCheck">
|
||||||
|
Show link check message tab
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3" v-if="mailbox.uiConfig.SpamAssassin">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="spamCheck"
|
||||||
|
v-model="mailbox.showSpamCheck">
|
||||||
|
<label class="form-check-label" for="spamCheck">
|
||||||
|
Show spam check message tab
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -20,7 +20,6 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
enabled: true,
|
|
||||||
check: false,
|
check: false,
|
||||||
platforms: [],
|
platforms: [],
|
||||||
allPlatforms: {
|
allPlatforms: {
|
||||||
@ -37,7 +36,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.enabled = !localStorage.getItem('htmlCheckDisabled')
|
|
||||||
this.loadConfig()
|
this.loadConfig()
|
||||||
this.doCheck()
|
this.doCheck()
|
||||||
},
|
},
|
||||||
@ -46,7 +44,7 @@ export default {
|
|||||||
summary: function () {
|
summary: function () {
|
||||||
let self = this
|
let self = this
|
||||||
|
|
||||||
if (!this.enabled || !this.check) {
|
if (!this.check) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,23 +197,19 @@ export default {
|
|||||||
platforms(v) {
|
platforms(v) {
|
||||||
localStorage.setItem('html-check-platforms', JSON.stringify(v))
|
localStorage.setItem('html-check-platforms', JSON.stringify(v))
|
||||||
},
|
},
|
||||||
enabled(v) {
|
// enabled(v) {
|
||||||
if (!v) {
|
// if (!v) {
|
||||||
localStorage.setItem('htmlCheckDisabled', true)
|
// localStorage.setItem('htmlCheckDisabled', true)
|
||||||
this.$emit('setHtmlScore', false)
|
// this.$emit('setHtmlScore', false)
|
||||||
} else {
|
// } else {
|
||||||
localStorage.removeItem('htmlCheckDisabled')
|
// localStorage.removeItem('htmlCheckDisabled')
|
||||||
this.doCheck()
|
// this.doCheck()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
doCheck: function () {
|
doCheck: function () {
|
||||||
if (!this.enabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.check = false
|
this.check = false
|
||||||
|
|
||||||
if (this.message.HTML == "") {
|
if (this.message.HTML == "") {
|
||||||
@ -314,20 +308,6 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="!enabled">
|
|
||||||
<h2 class="h4 text-secondary">HTML check is currently disabled</h2>
|
|
||||||
<p class="text-secondary">
|
|
||||||
This feature is currently in beta. Constructive feedback is welcome via
|
|
||||||
<a href="https://github.com/axllent/mailpit/issues" target="_blank">GitHub</a>.
|
|
||||||
</p>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" v-model="enabled" id="inlineEnableHTMLCheck">
|
|
||||||
<label class="form-check-label" for="inlineEnableHTMLCheck">
|
|
||||||
Enable HTML check
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="summary">
|
<template v-if="summary">
|
||||||
<div class="mt-5 mb-3">
|
<div class="mt-5 mb-3">
|
||||||
<div class="row w-100">
|
<div class="row w-100">
|
||||||
@ -368,10 +348,6 @@ export default {
|
|||||||
<i class="bi bi-info-circle-fill"></i>
|
<i class="bi bi-info-circle-fill"></i>
|
||||||
Help
|
Help
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#HTMLCheckOptions">
|
|
||||||
<i class="bi bi-gear-fill"></i>
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
@ -635,36 +611,4 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="modal fade" id="HTMLCheckOptions" tabindex="-1" aria-labelledby="HTMLCheckOptionsLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h1 class="modal-title fs-5" id="HTMLCheckOptionsLabel">HTML check options</h1>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>
|
|
||||||
HTML check is currently in beta. Constructive feedback is welcome via
|
|
||||||
<a href="https://github.com/axllent/mailpit/issues" target="_blank">GitHub</a>.
|
|
||||||
</p>
|
|
||||||
<div class="form-check form-switch mb-3">
|
|
||||||
<input class="form-check-input" type="checkbox" role="switch" v-model="enabled"
|
|
||||||
id="HTMLCheckSwitch">
|
|
||||||
<label class="form-check-label" for="HTMLCheckSwitch">
|
|
||||||
<template v-if="enabled">HTML check is enabled in the web UI</template>
|
|
||||||
<template v-else>HTML check is disabled in the web UI</template>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<p class="mt-4 small text-center text-secondary">
|
|
||||||
HTML check can be globally disabled with <code>--disable-html-check</code><br>
|
|
||||||
Remote CSS and font support can be globally blocked with <code>--block-remote-css-and-fonts</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -69,6 +69,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasAnyChecksEnabled: function() {
|
||||||
|
return (mailbox.showHTMLCheck && this.message.HTML)
|
||||||
|
|| mailbox.showLinkCheck
|
||||||
|
|| (mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
let self = this
|
let self = this
|
||||||
self.canSaveTags = false
|
self.canSaveTags = false
|
||||||
@ -263,7 +271,7 @@ export default {
|
|||||||
<tr class="small">
|
<tr class="small">
|
||||||
<th>To</th>
|
<th>To</th>
|
||||||
<td class="privacy">
|
<td class="privacy">
|
||||||
<span v-if="message.To && message.To.length" v-for="( t, i ) in message.To ">
|
<span v-if="message.To && message.To.length" v-for="(t, i) in message.To">
|
||||||
<template v-if="i > 0">, </template>
|
<template v-if="i > 0">, </template>
|
||||||
<span>
|
<span>
|
||||||
<span class="text-spaces">{{ t.Name }}</span>
|
<span class="text-spaces">{{ t.Name }}</span>
|
||||||
@ -278,7 +286,7 @@ export default {
|
|||||||
<tr v-if="message.Cc && message.Cc.length" class="small">
|
<tr v-if="message.Cc && message.Cc.length" class="small">
|
||||||
<th>Cc</th>
|
<th>Cc</th>
|
||||||
<td class="privacy">
|
<td class="privacy">
|
||||||
<span v-for="( t, i ) in message.Cc ">
|
<span v-for="(t, i) in message.Cc">
|
||||||
<template v-if="i > 0">,</template>
|
<template v-if="i > 0">,</template>
|
||||||
<span class="text-spaces">{{ t.Name }}</span>
|
<span class="text-spaces">{{ t.Name }}</span>
|
||||||
<<a :href="searchURI(t.Address)" class="text-body">
|
<<a :href="searchURI(t.Address)" class="text-body">
|
||||||
@ -423,16 +431,16 @@ export default {
|
|||||||
role="tab" aria-controls="nav-raw" aria-selected="false">
|
role="tab" aria-controls="nav-raw" aria-selected="false">
|
||||||
Raw
|
Raw
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown d-xl-none">
|
<div class="dropdown d-xl-none" v-show="hasAnyChecksEnabled">
|
||||||
<button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
<button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown"
|
||||||
aria-expanded="false">
|
aria-expanded="false">
|
||||||
Checks
|
Checks
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu checks">
|
<ul class="dropdown-menu checks">
|
||||||
<li>
|
<li v-if="mailbox.showHTMLCheck && message.HTML != ''">
|
||||||
<button class="dropdown-item" id="nav-html-check-tab" data-bs-toggle="tab"
|
<button class="dropdown-item" id="nav-html-check-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#nav-html-check" type="button" role="tab" aria-controls="nav-html"
|
data-bs-target="#nav-html-check" type="button" role="tab" aria-controls="nav-html"
|
||||||
aria-selected="false" v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''">
|
aria-selected="false">
|
||||||
HTML Check
|
HTML Check
|
||||||
<span class="badge rounded-pill p-1 float-end" :class="htmlScoreColor"
|
<span class="badge rounded-pill p-1 float-end" :class="htmlScoreColor"
|
||||||
v-if="htmlScore !== false">
|
v-if="htmlScore !== false">
|
||||||
@ -440,7 +448,7 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li v-if="mailbox.showLinkCheck">
|
||||||
<button class="dropdown-item" id="nav-link-check-tab" data-bs-toggle="tab"
|
<button class="dropdown-item" id="nav-link-check-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
|
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
|
||||||
aria-selected="false">
|
aria-selected="false">
|
||||||
@ -453,7 +461,7 @@ export default {
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="mailbox.uiConfig.SpamAssassin">
|
<li v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
|
||||||
<button class="dropdown-item" id="nav-spam-check-tab" data-bs-toggle="tab"
|
<button class="dropdown-item" id="nav-spam-check-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#nav-spam-check" type="button" role="tab" aria-controls="nav-html"
|
data-bs-target="#nav-spam-check" type="button" role="tab" aria-controls="nav-html"
|
||||||
aria-selected="false">
|
aria-selected="false">
|
||||||
@ -469,7 +477,7 @@ export default {
|
|||||||
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-html-check-tab"
|
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-html-check-tab"
|
||||||
data-bs-toggle="tab" data-bs-target="#nav-html-check" type="button" role="tab"
|
data-bs-toggle="tab" data-bs-target="#nav-html-check" type="button" role="tab"
|
||||||
aria-controls="nav-html" aria-selected="false"
|
aria-controls="nav-html" aria-selected="false"
|
||||||
v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''">
|
v-if="mailbox.showHTMLCheck && message.HTML != ''">
|
||||||
HTML Check
|
HTML Check
|
||||||
<span class="badge rounded-pill p-1" :class="htmlScoreColor" v-if="htmlScore !== false">
|
<span class="badge rounded-pill p-1" :class="htmlScoreColor" v-if="htmlScore !== false">
|
||||||
<small>{{ Math.floor(htmlScore) }}%</small>
|
<small>{{ Math.floor(htmlScore) }}%</small>
|
||||||
@ -477,7 +485,7 @@ export default {
|
|||||||
</button>
|
</button>
|
||||||
<button class="d-none d-xl-inline-block nav-link" id="nav-link-check-tab" data-bs-toggle="tab"
|
<button class="d-none d-xl-inline-block nav-link" id="nav-link-check-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
|
data-bs-target="#nav-link-check" type="button" role="tab" aria-controls="nav-link-check"
|
||||||
aria-selected="false">
|
aria-selected="false" v-if="mailbox.showLinkCheck">
|
||||||
Link Check
|
Link Check
|
||||||
<i class="bi bi-check-all text-success" v-if="linkCheckErrors === 0"></i>
|
<i class="bi bi-check-all text-success" v-if="linkCheckErrors === 0"></i>
|
||||||
<span class="badge rounded-pill bg-danger" v-else-if="linkCheckErrors > 0">
|
<span class="badge rounded-pill bg-danger" v-else-if="linkCheckErrors > 0">
|
||||||
@ -486,7 +494,7 @@ export default {
|
|||||||
</button>
|
</button>
|
||||||
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-spam-check-tab"
|
<button class="d-none d-xl-inline-block nav-link position-relative" id="nav-spam-check-tab"
|
||||||
data-bs-toggle="tab" data-bs-target="#nav-spam-check" type="button" role="tab"
|
data-bs-toggle="tab" data-bs-target="#nav-spam-check" type="button" role="tab"
|
||||||
aria-controls="nav-html" aria-selected="false" v-if="mailbox.uiConfig.SpamAssassin">
|
aria-controls="nav-html" aria-selected="false" v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
|
||||||
Spam Analysis
|
Spam Analysis
|
||||||
<span class="badge rounded-pill" :class="spamScoreColor" v-if="spamScore !== false">
|
<span class="badge rounded-pill" :class="spamScoreColor" v-if="spamScore !== false">
|
||||||
<small>{{ spamScore }}</small>
|
<small>{{ spamScore }}</small>
|
||||||
@ -537,16 +545,16 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="nav-html-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
|
<div class="tab-pane fade" id="nav-html-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
|
||||||
tabindex="0">
|
tabindex="0">
|
||||||
<HTMLCheck v-if="!mailbox.uiConfig.DisableHTMLCheck && message.HTML != ''" :message="message"
|
<HTMLCheck v-if="mailbox.showHTMLCheck && message.HTML != ''"
|
||||||
@setHtmlScore="(n) => htmlScore = n" @set-badge-style="(v) => htmlScoreColor = v" />
|
:message="message" @setHtmlScore="(n) => htmlScore = n" @set-badge-style="(v) => htmlScoreColor = v" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="nav-spam-check" role="tabpanel" aria-labelledby="nav-spam-check-tab"
|
<div class="tab-pane fade" id="nav-spam-check" role="tabpanel" aria-labelledby="nav-spam-check-tab"
|
||||||
tabindex="0">
|
tabindex="0" v-if="mailbox.showSpamCheck && mailbox.uiConfig.SpamAssassin">
|
||||||
<SpamAssassin v-if="mailbox.uiConfig.SpamAssassin" :message="message"
|
<SpamAssassin :message="message"
|
||||||
@setSpamScore="(n) => spamScore = n" @set-badge-style="(v) => spamScoreColor = v" />
|
@setSpamScore="(n) => spamScore = n" @set-badge-style="(v) => spamScoreColor = v" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="nav-link-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
|
<div class="tab-pane fade" id="nav-link-check" role="tabpanel" aria-labelledby="nav-html-check-tab"
|
||||||
tabindex="0">
|
tabindex="0" v-if="mailbox.showLinkCheck">
|
||||||
<LinkCheck :message="message" @setLinkErrors="(n) => linkCheckErrors = n" />
|
<LinkCheck :message="message" @setLinkErrors="(n) => linkCheckErrors = n" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -122,7 +122,11 @@ export default {
|
|||||||
value: p,
|
value: p,
|
||||||
color: c
|
color: c
|
||||||
},
|
},
|
||||||
];
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
scoreColor: function() {
|
||||||
|
return this.graphSections[0].color
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ export const mailbox = reactive({
|
|||||||
count: 0, // total in mailbox or search
|
count: 0, // total in mailbox or search
|
||||||
messages: [], // current messages
|
messages: [], // current messages
|
||||||
tags: [], // all tags
|
tags: [], // all tags
|
||||||
showTagColors: true, // show/hide tag colors
|
|
||||||
selected: [], // currently selected
|
selected: [], // currently selected
|
||||||
connected: false, // websocket connection
|
connected: false, // websocket connection
|
||||||
searching: false, // current search, false for none
|
searching: false, // current search, false for none
|
||||||
@ -19,7 +18,13 @@ export const mailbox = reactive({
|
|||||||
appInfo: {}, // application information
|
appInfo: {}, // application information
|
||||||
uiConfig: {}, // configuration for UI
|
uiConfig: {}, // configuration for UI
|
||||||
lastMessage: false, // return scrolling
|
lastMessage: false, // return scrolling
|
||||||
timeZone: '', // browser timezone
|
|
||||||
|
// settings
|
||||||
|
showTagColors: !localStorage.getItem('hideTagColors') == '1',
|
||||||
|
showHTMLCheck: !localStorage.getItem('hideHTMLCheck') == '1',
|
||||||
|
showLinkCheck: !localStorage.getItem('hideLinkCheck') == '1',
|
||||||
|
showSpamCheck: !localStorage.getItem('hideSpamCheck') == '1',
|
||||||
|
timeZone: localStorage.getItem('timeZone') ? localStorage.getItem('timeZone') : Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -39,3 +44,47 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mailbox.showHTMLCheck,
|
||||||
|
(v) => {
|
||||||
|
if (v) {
|
||||||
|
localStorage.removeItem('hideHTMLCheck')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('hideHTMLCheck', '1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mailbox.showLinkCheck,
|
||||||
|
(v) => {
|
||||||
|
if (v) {
|
||||||
|
localStorage.removeItem('hideLinkCheck')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('hideLinkCheck', '1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mailbox.showSpamCheck,
|
||||||
|
(v) => {
|
||||||
|
if (v) {
|
||||||
|
localStorage.removeItem('hideSpamCheck')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('hideSpamCheck', '1')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mailbox.timeZone,
|
||||||
|
(v) => {
|
||||||
|
if (v == Intl.DateTimeFormat().resolvedOptions().timeZone) {
|
||||||
|
localStorage.removeItem('timeZone')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('timeZone', v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user